CF906E Reverses[PAM+DP+等差数列]

CF906E Reverses solutions

题目链接

真的无奈,这一个题我硬刚做了4个小时,好像有点不太值啊

但是这个东西真的挺难的

简单的想到可以枚举以当前点结尾的回文串,\(\mathcal{O(n^2)}\)转移

首先我们曾经在\(border\)理论中学过所有的\(border\)的长度是\(logn\)个等差数列

这里同样,我们不断寻找一个长度为\(n\)的回文串的最长回文子串,这些回文子串的长度仍然是\(logn\)个等差数列

这个挺好理解的,证明的话:

如果有一个超过\(\frac{n}{2}\)的回文子串的话,那么我们就可以和原串做差

通过不断地翻转可以将整个序列分成长度都为这个差值的很多段,这个画图可以理解

这就是其中一个等差数列,那么我们继续按照同样的方法处理这个等差数列中的最后一段

不断进行这个过程,就得到了这\(logn\)个等差数列

那么我们知道了这个东西有啥用??

我们发现,对于某一个等差数列来说,设他的公差为\(d\),有\(k\)

假如我们当前转移的是第\(x\)位,含有这个等差数列

那么第\(x-d\)位,同样含有这个等差数列,只不过只有\(k-1\)

所以我们完全可以根据这个关系一点点的往后挪动,使得复杂度降为\(\mathcal{O(nlogn)}\)

发现这个串是回文的,于是第\(x-d\)为代表的回文串就是当前串的最长回文后缀

\(PAM\)上的话就是\(fail\),于是我们就可以利用\(fail\)的值来转移了

但是你发现多出来的一项就是第\(x-d\)这一位的值,那么我们在构建回文自动机的时候

顺便找到第一个长度差值不相等的回文后缀,减去公差就是当前的最后一项

看代码,具体是构建回文树和状态转移的地方。

code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
inline int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int inf=0x3f3f3f3f;
const int N=1e6+5;
int n,ans;
pair<int,int> an[N];
char a[N],b[N],c[N];
int we[N],f[N],g[N],p[N];
bool com(pair<int,int> a,pair<int,int> b){
    if(a.first==b.first)return a.second>b.second;
    return a.first<b.first;
}
struct PAM{
    struct POT{
        int len,cha,fail,link,son[26];
        //cha 为当前点和fail点的长度差值,link 为不断跳fail得到的第一个差值不一样的点
    }tr[N];
    int seg,las;
    PAM(){
        seg=1;tr[0].fail=1;
        tr[1].len=-1;
    }
    int get_fail(int x,int i){
        while(c[i-tr[x].len-1]!=c[i])x=tr[x].fail;
        return x;
    }
    void ins(int x){
        int pos=get_fail(las,x);
        if(!tr[pos].son[c[x]-'a']){
            tr[++seg].fail=tr[get_fail(tr[pos].fail,x)].son[c[x]-'a'];
            tr[pos].son[c[x]-'a']=seg;
            tr[seg].len=tr[pos].len+2;
            tr[seg].cha=tr[seg].len-tr[tr[seg].fail].len;
            tr[seg].link=(tr[seg].cha==tr[tr[seg].fail].cha)?tr[tr[seg].fail].link:tr[seg].fail;
            //得到差值和link
        }
        las=tr[pos].son[c[x]-'a'];
        we[x]=las;
        return ;
    }
}pam;
bool vis[N];
signed main(){
    scanf("%s",a+1);
    n=strlen(a+1);
    scanf("%s",b+1);
    fo(i,1,n){//将两个串合并起来
        c[i*2-1]=a[i];
        c[i*2]=b[i];
    }n*=2;
    fo(i,1,n)pam.ins(i);//构建回文自动机
    memset(f,0x3f,sizeof(f));f[0]=0;
    for(int i=1;i<=n;i++){
        if(i%2==0&&c[i]==c[i-1])if(f[i]>f[i-2]){
            f[i]=f[i-2];g[i]=i-2;
            //长度为2的时候可以免费转移
        }
        int now=we[i];//找到当前节点在回文自动机中代表的节点
        while(now&&now!=1){
            p[now]=i-pam.tr[pam.tr[now].link].len-pam.tr[now].cha;
            //找到当前等差数列的最后一项,也就是最小的一项所对应的转移点

            if(pam.tr[now].cha==pam.tr[pam.tr[now].fail].cha&&f[p[now]]>f[p[pam.tr[now].fail]])p[now]=p[pam.tr[now].fail];
            //此处就是核心,利用上一次的等差数列,和当前多出来的最后一项进行比较,得到当前在这一等差数列中的最优转移点
            //p数组就是保存最优转移点用的
            //还有一个性质,因为在回文自动机上每一点对应的fail是一定的,所以每一点所处的等差数列公差也是一定的,所以不需要重新计算

            if(i%2==0&&f[i]>f[p[now]]+1){
                f[i]=f[p[now]]+1;
                g[i]=p[now];
            }
            now=pam.tr[now].link;
        }
    }
    ans=f[n];
    if(ans>=inf){puts("-1");return 0;}
    int now=n;
    while(now!=0){
        if(now-g[now]==2)now=g[now];
        else {
            an[f[now]].first=g[now]/2+1;
            an[f[now]].second=now/2;
            now=g[now];
        }
    }
    printf("%d\n",ans);
    fo(i,1,ans)printf("%d %d\n",an[i].first,an[i].second);
}
posted @ 2021-12-10 14:08  fengwu2005  阅读(49)  评论(0编辑  收藏  举报