BZOJ3620: 似乎在梦中见过的样子
Description
“Madoka,不要相信 QB!”伴随着 Homura 的失望地喊叫,Madoka 与 QB 签订了契约.
这是 Modoka 的一个噩梦,也同时是上个轮回中所发生的事.为了使这一次 Madoka 不再与 QB签订契约,Homura 决定在刚到学校的第一天就解决 QB.然而,QB 也是有许多替身的(但在第八话中的剧情显示它也有可能是无限重生的),不过,意志坚定的 Homura 是不会放弃的——她决定
消灭所有可能是 QB 的东西.现在,她已感受到附近的状态,并且把它转化为一个长度为 n 的字符串交给了学 OI 的你.
现在你从她的话中知道 , 所有形似于 A+B+A 的字串都是 QB 或它的替身 , 且len(A)>=k,len(B)>=1 (位置不同其他性质相同的子串算不同子串,位置相同但拆分不同的子串算同一子串),然后你必须尽快告诉 Homura 这个答案——QB 以及它的替身的数量.
Input
第一行一个字符串,第二行一个数 k
Output
仅一行一个数 ans,表示 QB 以及它的替身的数量
Sample Input
【样例输入 1】
aaaaa
1
【样例输入 2】
abcabcabc
2
aaaaa
1
【样例输入 2】
abcabcabc
2
Sample Output
【样例输出 1】
6
【样例输出 2】
8
6
【样例输出 2】
8
HINT
对于 100%的数据:n<=15000 , k<=100,且字符集为所有小写字母
开始想的是枚举两个位置算LCP,将可行的右端点区间打上标记,时间O(n^2)。
可惜常数太大。。。。
//TLE #include<cstdio> #include<cctype> #include<queue> #include<cmath> #include<cstring> #include<algorithm> #define lc ch[x][0] #define rc ch[x][1] #define rep(i,s,t) for(int i=s;i<=t;i++) #define dwn(i,s,t) for(int i=s;i>=t;i--) #define ren for(int i=first[x];i;i=next[i]) using namespace std; const int BufferSize=1<<16; char buffer[BufferSize],*head,*tail; inline char Getchar() { if(head==tail) { int l=fread(buffer,1,BufferSize,stdin); tail=(head=buffer)+l; } return *head++; } inline int read() { int x=0,f=1;char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } typedef long long ll; const int maxn=30010; int first[maxn],next[maxn],To[maxn],ToT,e; void AddEdge(int u,int v) { To[++e]=v;next[e]=first[u];first[u]=e; } int mn[maxn<<1][20],dep[maxn<<1],Log[maxn<<1],Cur,Pos[maxn]; void dfs(int x,int fa) { mn[++Cur][0]=x;Pos[x]=Cur; ren if(To[i]!=fa) dep[To[i]]=dep[x]+1,dfs(To[i],x),mn[++Cur][0]=x; } void init() { Log[0]=-1;rep(i,1,Cur) Log[i]=Log[i>>1]+1; for(int j=1;(1<<j)<=Cur;j++) for(int i=0;i+(1<<j)-1<=Cur;i++) { int x=mn[i][j-1],y=mn[i+(1<<j-1)][j-1]; mn[i][j]=dep[x]<dep[y]?x:y; } } int lca(int x,int y) { x=Pos[x];y=Pos[y];if(x>y) swap(x,y); int k=Log[y-x+1];x=mn[x][k];y=mn[y-(1<<k)+1][k]; return dep[x]<dep[y]?x:y; } int n,m,to[maxn][26],pos[maxn],fa[maxn],l[maxn],cnt=1,last=1; void extend(int c,int x) { int p=last,q,np,nq;l[last=np=++cnt]=l[p]+1;pos[x]=np; for(;!to[p][c];p=fa[p]) to[p][c]=np; if(!p) fa[np]=1; else { q=to[p][c]; if(l[p]+1==l[q]) fa[np]=q; else { l[nq=++cnt]=l[p]+1; memcpy(to[nq],to[q],sizeof(to[q])); fa[nq]=fa[q];fa[q]=fa[np]=nq; for(;to[p][c]==q;p=fa[p]) to[p][c]=nq; } } } char s[maxn]; int S[maxn]; int main() { scanf("%s",s+1);int n=strlen(s+1); dwn(i,n,1) extend(s[i]-'a',i); rep(i,2,cnt) AddEdge(fa[i],i); dfs(1,0);init(); int k=read(),ans=0; rep(i,1,n-k-1) { rep(j,i+k*2-1,n) S[j]=0; rep(j,i+k+1,n) { int v=min(l[lca(pos[i],pos[j])],j-i-1); if(v>=k) S[k+j-1]++,S[v+j]--; } rep(j,i+k*2,n) { S[j]+=S[j-1]; if(S[j]) ans++; } } printf("%d\n",ans); return 0; }
这道题使用 KMP 做 O(n^2) 的暴力就能过。
首先,我们依次枚举字串左端点 l ,然后从这个左端点开始向后做一次 KMP。
然后我们枚举右端点 r ,符合条件的右端点 r 就是 S[l..r] 这一段的一个前缀和后缀相同,并且这一部分的长度 x 要满足 k <= x < (r - l + 1) / 2。
我们就使用 KMP 的 Next 数组来找这个前缀,直接从 Next[r] 向前找最坏情况会导致变成 O(n^3) ,所以我们应该优化一下:
Next 数组如果视为 Father 数组,那么它是一棵树。我们求的就是一个节点到根的路径上有没有区间 [k, (r-l+1)/2) 的数。
我们求 Next 的时候是从根向叶子求的,所以我们可以同时维护根到某个节点 j 的所有数中,大于等于 k 的数中最小的一个。
然后我们对于每一个右端点,只要判断这个点到根的路径中大于等于 k 的数中最小的一个是否小于 (r - l + 1) / 2 就可以了。
#include<cstdio> #include<cctype> #include<queue> #include<cmath> #include<cstring> #include<algorithm> #define rep(i,s,t) for(int i=s;i<=t;i++) #define dwn(i,s,t) for(int i=s;i>=t;i--) #define ren for(int i=first[x];i;i=next[i]) using namespace std; const int BufferSize=1<<16; char buffer[BufferSize],*head,*tail; inline char Getchar() { if(head==tail) { int l=fread(buffer,1,BufferSize,stdin); tail=(head=buffer)+l; } return *head++; } inline int read() { int x=0,f=1;char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } const int maxn=15010; char s[maxn]; int n,k,ans,f[maxn],g[maxn]; int main() { scanf("%s",s+1);n=strlen(s+1);k=read(); rep(i,1,n) { f[1]=0;g[0]=1e9;int j=0; rep(p,2,n-i+1) { while(j&&s[i+j]!=s[i+p-1]) j=f[j]; if(s[i+j]==s[i+p-1]) j++;f[p]=j; if(j<k) g[j]=1e9; else g[j]=min(j,g[f[j]]); if((g[j]<<1)<p) ans++; } } printf("%d\n",ans); return 0; }