失配樹
阿新 • • 發佈:2021-10-30
名字看起來挺高階的,然而其實就是 \(\text{KMP}\) 上樹啦。
我們將每個點的 \(nex[i]\) 與 \(i\) 連邊,那麼最終 \(border\) 關係會形成一棵樹,之後就可以在樹上搞事情啦!
這題比較裸,直接根據定義建樹之後對於兩個字首求出在 \(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 大聲喊道:我發明了失配樹!
題意:給你一個長度為 \(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; }
這道題可以根本不用往失配樹去理解,我們發現每一次最長合法 \(\text{border}\) 的長度最多隻會增加 \(1\),那麼直接暴力跑 \(\text{KMP}\) 並及時處理 \(\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;
}