NOIP2024模擬賽13:拆開未來
C-重複
-
一句話題意:給定字串 \(S\), 問 \(S\) 的所有子串共有多少種“好的拆分方案”。對於一個字串 \(S\), 一個劃分是好的當且僅當能把 \(S\) 劃分成 6 個非空子串 \(a,b,c,d,e\), 滿足 \(a=b=e, \ c=f\) (一個字串可能有多種劃分方式)
-
標籤:字首和,思維,雜湊表(雜湊)
-
簡化一下就是要滿足 \(AABCAB\).
-
60分的暴力是對每一個區間列舉開頭的 \(A\) 和結尾的 \(B\). (P.S.模數設 \(998244353\) 會被卡!!!)
const int N=5005; const ll P=131; ull h[N],p[N]; char s[N]; int n; ll ans=0; ull get(int l,int r){ return h[r]-h[l-1]*p[r-l+1]; } void solve(int l,int r){ int len=r-l+1; F(i,1,len/3) for(int j=1;j<=len/2 && 3*i+2*j<len;++j){ ull A=get(l,l+i-1),B=get(l+i,l+2*i-1),C=get(l+2*i,l+2*i+j-1),E=get(r-i-j+1,r-j),F=get(r-j+1,r); if(A == B && B == E && C == F) ++ans; } } signed main(){ freopen("c.in","r",stdin); freopen("c.out","w",stdout); ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin>>(s+1); n=strlen(s+1); p[0]=1; F(i,1,n) h[i]=h[i-1]*P+s[i]-'0',p[i]=p[i-1]*P; F(l,1,n) F(r,l+5,n) solve(l,r); cout<<ans; return 0; }
-
但正如鄧老師所說,這種拆分方式是“不平衡的”,雖然我們可以用 \(O(1)\) 的時間去檢查剩下的位置,但我們卻需要花 \(O(N^2)\) 的時間列舉所有的首尾情況。
-
所以正解我們選擇列舉 \(AB\). 列舉 \(A\) 的左端點 \(i\) 和 \(B\) 的右端點 \(j\).
-
對於 \(AA\), 在固定 \(i\) 的情況下可以用字首和處理所有貢獻。
-
對於 \(AB\) 和後面那個 \(AB\) 的匹配, 把所有已經掃到過的 \(AB\) 存進一個雜湊表並統計其個數(\(unorderedmap\) 會 T掉)
-
兩者的貢獻相乘即是單次的總貢獻。
-
注意迴圈的初末條件,\(C\) 不為空(詳見程式碼)
#include<bits/stdc++.h>
#define F(i,l,r) for(register int i(l);i<=r;++i)
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N=5005;
const ll P=131;
ull h[N],p[N];
char s[N];
int n; ll ans=0;
ull get(int l,int r){ return h[r]-h[l-1]*p[r-l+1]; }
void solve(int l,int r){
int len=r-l+1;
F(i,1,len/3) for(int j=1;j<=len/2 && 3*i+2*j<len;++j){
ull A=get(l,l+i-1),B=get(l+i,l+2*i-1),C=get(l+2*i,l+2*i+j-1),E=get(r-i-j+1,r-j),F=get(r-j+1,r);
if(A == B && B == E && C == F) ++ans;
}
}
signed main(){
freopen("c.in","r",stdin); freopen("c.out","w",stdout);
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>(s+1); n=strlen(s+1); p[0]=1;
F(i,1,n) h[i]=h[i-1]*P+s[i]-'0',p[i]=p[i-1]*P;
F(l,1,n) F(r,l+5,n) solve(l,r);
cout<<ans;
return 0;
}