最近覚えたテクを使ってみたかったんだ…。
http://yukicoder.me/problems/440
問題
要素数Lの集合Aと要素数Mの集合Bがある。
両集合の要素は1~Nの整数である。
集合Bvを、Bの各要素にvを加算して得られる集合とする。
0~(Q-1)の各整数vについて、を出力せよ。
解法
想定解はbit畳み込み。
bitsetや整数配列を用いてNbitのbitmapを作り、Bに対応するbitmapを1 bitずつシフトしながらAのbitmapとandを取りbit数をカウントする。
この解法の計算量はO(N*Q)だがbit畳み込みによる定数倍高速化により何とか間に合う。
自分は本番Nの上限を10^6と勘違いして「bit畳み込みでN*Q/64回ループしても間に合わないなー」と思い別解法でACした。
自分の解法は高速フーリエ変換を使った方法で、計算量はO(N*logN+Q)であり実際Nが10^6でも4秒弱で間に合う。
A,Bを数列とみなし、そこから生成できる多項式を考える。
両多項式の積 において、係数
は
、すなわち
であるような(i,j)の組の数に相当する。
これは問題で要求される値そのものである。
よって、高速フーリエ変換を用いて多項式乗算を行い、を求めればそれが解となる。
int L,M,N,Q; typedef complex<double> Comp; vector<Comp> fft(vector<Comp> v, bool rev=false) { int n=v.size(),i,j,m; for(i=0,j=1;j<n-1;j++) { for(int k=n>>1;k>(i^=k);k>>=1); if(i>j) swap(v[i],v[j]); } for(int m=2; m<=n; m*=2) { double deg=(rev?-1:1) * 2*acos(-1)/m; Comp wr(cos(deg),sin(deg)); for(i=0;i<n;i+=m) { Comp w(1,0); for(int j1=i,j2=i+m/2;j2<i+m;j1++,j2++) { Comp t1=v[j1],t2=w*v[j2]; v[j1]=t1+t2, v[j2]=t1-t2; w*=wr; } } } if(rev) FOR(i,n) v[i]*=1.0/n; return v; } vector<Comp> MultPoly(vector<Comp> P,vector<Comp> Q) { P=fft(P), Q=fft(Q); for(int i=0;i<P.size();i++) P[i]*=Q[i]; return fft(P,true); } void solve() { int i,j,k,l,r,x,y; string s; vector<Comp> AC(1<<18,0),BC(1<<18,0); cin>>L>>M>>N; FOR(i,L) cin>>x, AC[x]+=1; FOR(i,M) cin>>x, BC[N-x]+=1; cin>>Q; auto CC=MultPoly(AC,BC); FOR(i,Q) _P("%d\n",(int)(CC[i+N].real()+0.1)); }
まとめ
お手軽bitset解を思いつかなかったのは悔しいが、最近覚えた高速フーリエ変換を復習できたのは良かった。