あまり良い手段ではないが何とか解けた。
http://codeforces.com/contest/471/problem/D
問題
幅1、高さa[i]の建物がN個順に並んでいる。
ここで、幅1、高さb[i]の建物がW個連続にならんでいる場合、その建物群は象に見える。
また、象に見える建物群を平行移動したら一致する建物群も象に見える。
元のN個の建物の中に、象は何個見えるか。
解法
W==1の時、答えはNとなる。
それ以外の場合、a[i]の差分を取って(N-1)要素の配列p[i]を作る。
また、b[i]の差分を取って(W-1)要素の配列q[i]を作る。
差分を取ることに気づけばあとは簡単で、p[i]中にq[i]が何個含まれるか調べればよい。
これは文字列検索と同じなので、ローリングハッシュ・KMP法・Z Algorithmあたりでいずれも通る。
自分は本番ローリングハッシュを使ったけど、Z Algorithmの方が簡単だね。
以下はローリングハッシュ。
int N,W; ll A[300000],B[300000]; ll D[300000],E[300000]; ll mo1=999999937,mo2=1000000009; ll d1,d2; void solve() { int i,j,k,l,r,x,y; string s; cin>>N>>W; FOR(i,N) cin>>A[i]; FOR(i,W) cin>>B[i]; N--;W--; FOR(i,N) D[i]=A[i+1]-A[i]; FOR(i,W) E[i]=B[i+1]-B[i]; ll ha1=0,ha2=0; d1=d2=1; ll m1=10009,m2=10007; ll a1=1000000000+10007,a2=1000000000+3333331; if(N<W) return _P("0\n"); if(W==0) return _P("%d\n",N+1); FOR(i,W) { ha1=(ha1*m1+E[i]+a1)%mo1; ha2=(ha2*m2+E[i]+a2)%mo2; d1=d1*m1%mo1; d2=d2*m2%mo2; } ll c1=0,c2=0; int ret=0,t=0; FOR(i,N) { c1=(c1*m1+D[i]+a1)%mo1; c2=(c2*m2+D[i]+a2)%mo2; t++; if(t>=W+1) { c1-=d1*(D[i-W]+a1)%mo1; c2-=d2*(D[i-W]+a2)%mo2; if(c1<0) c1+=mo1; if(c2<0) c2+=mo2; } if(t>=W && c1==ha1 &&c2==ha2) { ret++; /*i++; t=0; c1=c2=0;*/ } } cout << ret << endl; }
以下はKMP法。
int N,W; ll A[300000],B[300000]; int KMPT[4*100000]; ll res; int KMP(vector<int>& tar,vector<int>& pat, int cur=0) { int c,p=0,l,pl; FOR(c,tar.size()) { while(cur>-1 && pat[cur]!=tar[c]) cur=KMPT[cur]; if(++cur >= pat.size()) res++, cur=KMPT[cur]; } return cur; } void prepKMP(vector<int>& str) { int i,n; n=KMPT[0]=-1; FOR(i,str.size()) { while(n>-1 && str[i]!=str[n]) n=KMPT[n]; KMPT[i+1]=++n; } } void solve() { int i,j,k,l,r,x,y; string s; cin>>N>>W; FOR(i,N) cin>>A[i]; FOR(i,W) cin>>B[i]; if(W==1) return _P("%d\n",N); vector<int> D,E; FOR(i,N-1) D.push_back(A[i+1]-A[i]); FOR(i,W-1) E.push_back(B[i+1]-B[i]); prepKMP(E); KMP(D,E); cout << res << endl; }
Z Algorithmでも行ける。
int N,W; ll A[300000],B[300000]; vector<int> Zalgo(vector<int> s) { int l=-1,r=-1,i; vector<int> v; v.push_back(s.size()); for(i=1;i<s.size();i++) { if(i<=r && v[i-l]<r-i+1) v.push_back(v[i-l]); else { l=i; r=(i>r)?i:(r+1); while(r<s.size() && s[r-i]==s[r]) r++; v.push_back((r--)-l); } } return v; } void solve() { int i,j,k,l,r,x,y; string s; cin>>N>>W; FOR(i,N) cin>>A[i]; FOR(i,W) cin>>B[i]; if(W==1) return _P("%d\n",N); vector<int> V; FOR(i,W-1) V.push_back(B[i+1]-B[i]); V.push_back(1<<30); FOR(i,N-1) V.push_back(A[i+1]-A[i]); V=Zalgo(V); int res=0; for(i=W;i<V.size();i++) if(V[i]>=W-1) res++; cout << res << endl; }
まとめ
Writeは差分を取ることに気づく人があまりいないと思ってこの問題をDに置いたようだが、想像以上に気づいた人は多かったようだ。
自分もわりとあっさり気づいたしね。