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

Sample Output

【样例输出 1】
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;
}
View Code

这道题使用 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;
}
View Code

 

 
posted @ 2015-12-16 12:40  wzj_is_a_juruo  阅读(243)  评论(0编辑  收藏  举报