失配树
名字看起來挺高級的,然而其實就是 \(\text{KMP}\) 上樹啦。
我們將每個點的 \(nex[i]\) 與 \(i\) 連邊,那么最終 \(border\) 關系會形成一棵樹,之后就可以在樹上搞事情啦!
P5829 【模板】失配樹
這題比較裸,直接根據定義建樹之后對于兩個前綴求出在 \(fail\) 樹上的最近公共祖先即可。
$\texttt{code}$ // Author:A weak man named EricQian #include<bits/stdc++.h> using namespace std; #define infll 0x7f7f7f7f7f7f7f7f #define inf 0x3f3f3f3f #define Maxn 1000005 #define Maxpown 22 typedef long long ll; inline int rd() {int x=0;char ch,t=0;while(!isdigit(ch = getchar())) t|=ch=='-';while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();return x=t?-x:x; } int n,q,tot; int pre[Maxn],dep[Maxn]; int fa[Maxn][Maxpown]; char s[Maxn]; inline int query(int x,int y) {if(dep[x]>dep[y]) swap(x,y);for(int i=20;i>=0;i--) if(dep[fa[y][i]]>=dep[x]) y=fa[y][i];if(x==y) return fa[x][0];for(int i=20;i>=0;i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];return fa[x][0]; } int main() {//ios::sync_with_stdio(false); cin.tie(0);//freopen(".in","r",stdin);//freopen(".out","w",stdout);scanf("%s",s+1),n=strlen(s+1);for(int i=2,j=0;i<=n;i++){while(j && s[i]!=s[j+1]) j=pre[j];if(s[i]==s[j+1]) j++;pre[i]=j,fa[i][0]=j,dep[i]=dep[j]+1;}for(int i=1;i<=20;i++)for(int j=1;j<=n;j++)fa[j][i]=fa[fa[j][i-1]][i-1];q=rd();for(int i=1,x,y;i<=q;i++) x=rd(),y=rd(),printf("%d\n",query(x,y));//fclose(stdin);//fclose(stdout);return 0; }小插曲:(小聲)
ZCETHAN 告訴 EricQian 這一題用失配樹做,EricQian 立刻表示它不會失配樹?!具^了 5 秒】EricQian 大聲喊道:我發明了失配樹!
CF432D Prefixes and Suffixes
題意:給你一個長度為 \(n\) 的長字符串,“完美子串”既是它的前綴也是它的后綴,求“完美子串”的個數且統計這些子串的在長字符串中出現的次數。
我們發現對于一個前綴的出現個數其實就是:(難以表述直接用偽代碼列出了)
len=這個前綴的長度 for(int i=1;i<=n;i++)for(int j=i;j;j=nex[j])ans+=(j==len)?1:0;之后我們發現對于同一個前綴它可能被訪問多次,這個可以直接倒換循環順序實現 \(O(n)\) 解決。(于是你就發明的失配樹)
$\texttt{code}$ // Author:A weak man named EricQian #include<bits/stdc++.h> using namespace std; #define infll 0x7f7f7f7f7f7f7f7f #define inf 0x3f3f3f3f #define Maxn 100005 typedef long long ll; inline int rd() {int x=0;char ch,t=0;while(!isdigit(ch = getchar())) t|=ch=='-';while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();return x=t?-x:x; } inline ll maxll(ll x,ll y){ return x>y?x:y; } inline ll minll(ll x,ll y){ return x<y?x:y; } inline ll absll(ll x){ return x>0ll?x:-x; } inline ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); } int n,tot; int nex[Maxn],cnt[Maxn]; int ans[Maxn][2]; char s[Maxn]; int main() {//ios::sync_with_stdio(false); cin.tie(0);//freopen(".in","r",stdin);//freopen(".out","w",stdout);scanf("%s",s+1),n=strlen(s+1);for(int i=2,j=0;i<=n;i++){while(j && s[i]!=s[j+1]) j=nex[j];if(s[i]==s[j+1]) j++;nex[i]=j;}for(int i=n;i>=1;i--) cnt[i]++,cnt[nex[i]]+=cnt[i]; // for(int i=1;i<=n;i++) // for(int j=i;j;j=nex[j]) cnt[j]++;ans[++tot][0]=n,ans[tot][1]=1;for(int i=nex[n];i;i=nex[i])ans[++tot][0]=i,ans[tot][1]=cnt[i];printf("%d\n",tot);for(int i=tot;i>=1;i--) printf("%d %d\n",ans[i][0],ans[i][1]);//fclose(stdin);//fclose(stdout);return 0; }P2375 [NOI2014] 動物園
這道題可以根本不用往失配樹去理解,我們發現每一次最長合法 \(\text{border}\) 的長度最多只會增加 \(1\),那么直接暴力跑 \(\text{KMP}\) 并及時處理 \(\text{border}\) 長度大于一半的情況即可。
一些正確性證明:如果這個位置的 \(\text{border}\) 長度大于了 \(\frac{i}{2}\),這個 \(\text{border}\) 的后綴起始點將永遠不再會成為答案,因此跳出這個 \(\text{border}\) 一定是對的。
$\texttt{code}$ // Author:A weak man named EricQian #include<bits/stdc++.h> using namespace std; #define infll 0x7f7f7f7f7f7f7f7f #define inf 0x3f3f3f3f #define Maxn 1000005 #define mod 1000000007 typedef long long ll; inline int rd() {int x=0;char ch,t=0;while(!isdigit(ch = getchar())) t|=ch=='-';while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();return x=t?-x:x; } inline ll maxll(ll x,ll y){ return x>y?x:y; } inline ll minll(ll x,ll y){ return x<y?x:y; } inline ll absll(ll x){ return x>0ll?x:-x; } inline ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); } int n,ans; char s[Maxn]; int nex[Maxn],dep[Maxn]; int main() {//ios::sync_with_stdio(false); cin.tie(0);//freopen(".in","r",stdin);//freopen(".out","w",stdout);int T=rd();while(T--){scanf("%s",s+1),n=strlen(s+1),ans=1;dep[1]=1;for(int i=2,j=0;i<=n;i++){while(j && s[i]!=s[j+1]) j=nex[j];if(s[i]==s[j+1]) j++;nex[i]=j,dep[i]=dep[j]+1;}for(int i=2,j=0;i<=n;i++){while(j && s[i]!=s[j+1]) j=nex[j];if(s[i]==s[j+1]) j++;while(j*2>i) j=nex[j];ans=1ll*ans*(1ll*dep[j]+1ll)%mod;}printf("%d\n",ans);}//fclose(stdin);//fclose(stdout);return 0; }P3426 [POI2005]SZA-Template
考慮設 \(dp(i)\) 表示主串的前綴 \([1,i]\) 需要的最短長度。
容易發現在失配樹上 \(dp(i)\) 沿著樹成單調遞增關系,考慮 \(dp(i)\) 與 \(dp(nex_i)\) 的關系。
如果 \(dp(i)\in (dp(nex_i),nex_i]\),由于 \([1,i]\) 的一個 border 為 \(dp(nex_i)\),所以 \(dp(nex_i)\) 也一定能夠生成這個串。
所以 \(dp(i)\) 能夠生成的串一定是 \(dp(nex_i)\) 能夠生成的真子集,即答案取 \(dp(nex_i)\) 一定比 \((dp(nex_i),nex_i]\) 優。
因此我們只用判斷能否由 \(dp(nex_i)\) 生成 \([1,i]\) 即可,具體即記錄 \(dp(nex_i)\) 能夠生成的最長串 \(s\),如果 \(|s|+|dp(nex_i)|\ge i\) 即可。
如果上述情況不成立,意味著 \(dp(i)=i\)。
$\texttt{code}$ // Author:A weak man named EricQian #include<bits/stdc++.h> using namespace std; #define infll 0x7f7f7f7f7f7f7f7f #define inf 0x3f3f3f3f #define Maxn 500005 typedef long long ll; inline int rd() {int x=0;char ch,t=0;while(!isdigit(ch = getchar())) t|=ch=='-';while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();return x=t?-x:x; } inline ll maxll(ll x,ll y){ return x>y?x:y; } inline ll minll(ll x,ll y){ return x<y?x:y; } inline ll absll(ll x){ return x>0ll?x:-x; } inline ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); } int n; int nex[Maxn],maxx[Maxn],dp[Maxn]; char s[Maxn]; int main() {//ios::sync_with_stdio(false); cin.tie(0);//freopen(".in","r",stdin);//freopen(".out","w",stdout);scanf("%s",s+1),n=strlen(s+1);for(int i=2,j=0;i<=n;i++){while(j && s[i]!=s[j+1]) j=nex[j];if(s[i]==s[j+1]) j++;nex[i]=j;}for(int i=1;i<=n;i++){if(maxx[dp[nex[i]]]+dp[nex[i]]>=i) dp[i]=dp[nex[i]];else dp[i]=i;maxx[dp[i]]=i;}printf("%d\n",dp[n]);//fclose(stdin);//fclose(stdout);return 0; }總結
 
                            
                        - 上一篇: 可持久化并查集
- 下一篇: 拉勾招聘:近七成00后理想月薪要过万 超
