1. 程式人生 > 其它 >【洛谷5330】[SNOI2019] 數論(數論)

【洛谷5330】[SNOI2019] 數論(數論)

點此看題面

  • 給定一個大小為\(n\)的整數集\(A\),一個大小為\(m\)的整數集\(B\)以及三個數\(P,Q,T\)
  • \(\sum_{i=0}^{T-1}[(i\% P\in A)\wedge(i\% Q\in B)]\)
  • \(n,m\le10^6\)

數論+建圖

考慮列舉\(a_i\in A\),去計算有多少個\((kP+a_i)\% Q\in B\)

發現\(P\)固定,每次\(k\)增加\(1\),從一個\(x=(kP+a_i)\%Q\)會變成的\(y=((k+1)P+a_i)\%Q\)是始終不變的。

於是我們建出一張圖,從每個\(x\)\((x+P)\%Q\)連一條邊,給所有\(b_i\)

打上標記,則問題就變成從\(a_i\)出發走\(\lfloor\frac{T-1-a_i}{P}\rfloor\)步能經過多少關鍵點。

顯然,這張圖由若干個環構成,對於每個環記下它的長度以及標記總和,並任選一個點把環斷開依次記下環上的每個點,求出標記的字首和。同時,記錄每個點所在環及在環中的位置。

那麼從一個點\(x\)出發走\(k\)步,假設所在環長為\(cnt\),所在位置為\(rk\)(位置標號為\(0\sim cnt-1\)),它將把整個環遍歷\(\lfloor\frac k{cnt}\rfloor\)次,並額外走過\(rk\sim (rk+k)\%cnt\)的所有點,後者利用我們預處理出的標記字首和可以方便計算。

程式碼:\(O(P+Q)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define LL long long
using namespace std;
int n,m,P,Q,a[N+5],b[N+5];LL T;
int id[N+5],rk[N+5],s[N+5],cnt[N+5],tot[N+5];vector<int> w[N+5];
int main()
{
	RI i,j;for(scanf("%d%d%d%d%lld",&P,&Q,&n,&m,&T),i=1;i<=n;++i) scanf("%d",a+i);
	for(i=1;i<=m;++i) scanf("%d",b+i),s[b[i]]=1;//打標記
	RI x,y,c=0;for(i=0;i^Q;++i) if(!id[i])
	{
		x=i,++c;W(!id[x]) w[id[x]=c].push_back(x),rk[x]=cnt[c]++,x=(x+P)%Q;//遍歷環,依次記錄所有點
		for(j=1;j^cnt[c];++j) s[w[c][j]]=s[w[c][j]]+s[w[c][j-1]];tot[c]=s[w[c][cnt[c]-1]];//求出標記的字首和,記錄整個環的標記總和
	}
	LL k,t=0;for(i=1;i<=n;++i) k=(T-1-a[i])/P,x=id[a[i]%Q],y=rk[a[i]%Q],//從第x個環的第y位出發走k步
		t+=k/cnt[x]*tot[x],k%=cnt[x],t+=(y+k<cnt[x]?s[w[x][y+k]]:tot[x]+s[w[x][y+k-cnt[x]]])-(y?s[w[x][y-1]]:0);//遍歷整環k/cnt[x]遍,額外走y~(y+k)%cnt[x]所有點
	return printf("%lld\n",t),0;
}