• 【CF932G】Palindrome Partition(回文树,动态规划)


    【CF932G】Palindrome Partition(回文树,动态规划)

    题面

    CF
    翻译:
    给定一个串,把串分为偶数段
    假设分为了(s1,s2,s3....sk)
    求,满足(s_1=s_k,s_2=s_{k-1}......)的方案数

    题解

    反正我是不会做
    基本就是照着(laofu)的打了一遍(laofu太强啦)

    这题分成了两个步骤
    如果直接分(k)段我们是没法直接判断的
    假设两段(s_i,s_{k-i+1})
    因为(s_i=s_{k-i+1}=x_1x_2.....x_j)
    假设(s_i)的开始位置为(p)
    假设原串(S)的长度为(n)
    (s_i=S[p]S[p+1]....S[p+j-1])
    (s_{k-i+1}=S[n-j-p+1]S[n-j-p+2]...S[n-p+1])
    对应相等的关系是
    (S[p]=S[n-p-j+1])
    如果(p=1)考虑一下特殊情况
    那么就是(S[1]=S[n-j])
    那么,如果我们有一个串(S')
    (S'=S[1]S[n]S[2]S[n-2].....)
    那么,对应到上面的相等关系里面,
    就是(S'[1..j]) 是一个回文串
    其他的每一组对应关系也是如此
    所以题目转换成了:
    告诉你了(S')回答把(S')分为若干个长度为偶数的回文串的方案数
    (如果没有搞清楚可以手玩一下)

    这个就是一个裸的(dp)
    (f[i])表示(S'[1..i])划分为若干长度为偶数的回文串的方案数
    得到转移方程:

    [f[i]=sum_{j}f[j-1] ]

    其中,(S'[j,i])是回文串
    那么,每一个(j)对应的位置相当于是(S'[1..i])的回文后缀
    构建出回文树之后沿着(last)(fail)就可以得到所有的(j)的位置
    然后我们就写出来了一份代码:

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define RG register
    #define MAX 1000100
    #define MOD 1000000007
    inline int read()
    {
        RG int x=0,t=1;RG char ch=getchar();
        while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
        if(ch=='-')t=-1,ch=getchar();
        while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
        return x*t;
    }
    char ch[MAX],s[MAX];
    int n,f[MAX];
    struct Palindromic_Tree
    {
    	struct Node
    	{
    		int son[26];
    		int ff,len;
    	}t[MAX];
    	int last,tot;
    	void init()
    	{
    		t[tot=1].len=-1;
    		t[0].ff=t[1].ff=1;
    	}
    	void extend(int c,int n,char *s)
    	{
    		int p=last;
    		while(s[n-t[p].len-1]!=s[n])p=t[p].ff;
    		if(!t[p].son[c])
    		{
    			int v=++tot,k=t[p].ff;
    			t[v].len=t[p].len+2;
    			while(s[n-t[k].len-1]!=s[n])k=t[k].ff;
    			t[v].ff=t[k].son[c];
    			t[p].son[c]=v;
    		}
    		last=t[p].son[c];
    	}
    }PT;
    int main()
    {
    	PT.init();
    	scanf("%s",ch+1);
    	n=strlen(ch+1);
    	if(n&1){puts("0");return 0;}
    	for(int i=1;i<=n;i+=2)s[i]=ch[(i+1)/2];
    	reverse(&ch[1],&ch[n+1]);
    	for(int i=2;i<=n;i+=2)s[i]=ch[(i+1)/2];
    	f[0]=1;
    	for(int i=1;i<=n;++i)
    	{
    		PT.extend(s[i]-97,i,s);
    		int p=PT.last;
    		while(p!=1)
    		{
    			if(PT.t[p].len%2==0&&PT.t[p].len>0)f[i]=(f[i]+f[i-PT.t[p].len])%MOD;
    			p=PT.t[p].ff;
    		}
    	}
    	printf("%d
    ",f[n]);
    	return 0;
    }
    

    然后在(CF)上交一发:

    这里写图片描述

    看一看数据是啥呢?

    这里写图片描述

    如果我们像这份代码一样,沿着(fail)一路上跳
    因为所有字符都相同
    所以要跳(O(n))
    复杂度变为了(O(n^2))
    完美(TLE)

    所以肯定就不能这么做。。。
    然后我就不会了


    假设对于某个位置,它对应的若干回文后缀

    这里写图片描述

    如果他们的长度(>len/2)
    可以证明他们的长度等差

    这里写图片描述

    证明?根据回文串的性质,上面的那些圈圈都是相等的串
    证毕

    如果把这一些看成一组
    每一组都至少要(÷ 2)
    所以至多只会有(log)
    这样能够保证复杂度,所以我们考虑能否一组一组转移
    (g[x])表示这一组东西的和
    因为一组不能直接在串中表示
    所以用回文树上的节点来表示
    所以,(g[x])表示从节点(x)开始,一直到缩短的值不再是当前这个等差的位置产生的贡献

    这里写图片描述

    比如对于这一组,它产生的贡献就是
    (g[x]=f[j1]+f[j2]+f[j3])
    (最底下那个是原串)

    好的,假设我们当前位置是(i)
    也就是最底下的原串是(S'[1..i])

    最长的回文后缀,也就是当前回文树上的(last)
    (p=last)
    因为是最长的一个,所以之前肯定不会计算过这个位置的值
    所以(g[p]=f[j_1])

    然后往上面的那些回文后缀看

    这里写图片描述

    这些红色的代表着关于下面的父亲对称的回文前缀
    我们惊奇的发现:
    它们的开始位置怎么就这么巧呢?
    正好就是我要算的东西
    也就是说(g[p]+=g[p.fail])
    当然,前提是(p.fail)还在这一组等差的串里面
    然后(p)就跳到第一个不是它所在的等差的回文后缀的位置
    所以(f[i]=sum_{p}g[p]),前提是(i\%2=0)

    最后,总结一下算法
    对于给定的串(S)
    把它变成(S'=S[1]S[n]S[2]S[n-1].....)
    然后依次构建回文树
    每个节点要记录一下它所在的这个等差的回文后缀在哪个节点结束
    然后对于每次的(last)节点向上跳
    跳到第一个不是自己这一段回文的位置
    因为每次至少要(÷2),所以至多跳(log)
    综上,时间复杂度(O(nlog))
    空间复杂度(O(nsum))(sum)代表字符集大小

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define RG register
    #define MAX 1000100
    #define MOD 1000000007
    inline int read()
    {
        RG int x=0,t=1;RG char ch=getchar();
        while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
        if(ch=='-')t=-1,ch=getchar();
        while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
        return x*t;
    }
    char ch[MAX],s[MAX];
    int n,anc[MAX],diff[MAX];
    int ans[MAX],f[MAX];
    struct Palindromic_Tree
    {
    	struct Node
    	{
    		int son[26];
    		int ff,len;
    	}t[MAX];
    	int last,tot;
    	void init()
    	{
    		t[tot=1].len=-1;
    		t[0].ff=t[1].ff=1;
    		anc[0]=1;
    	}
    	void extend(int c,int n,char *s)
    	{
    		int p=last;
    		while(s[n-t[p].len-1]!=s[n])p=t[p].ff;
    		if(!t[p].son[c])
    		{
    			int v=++tot,k=t[p].ff;
    			t[v].len=t[p].len+2;
    			while(s[n-t[k].len-1]!=s[n])k=t[k].ff;
    			t[v].ff=t[k].son[c];
    			t[p].son[c]=v;
    			diff[v]=t[v].len-t[t[v].ff].len;
    			anc[v]=(diff[v]==diff[t[v].ff])?anc[t[v].ff]:t[v].ff;
    		}
    		last=t[p].son[c];
    	}
    }PT;
    int main()
    {
    	PT.init();
    	scanf("%s",ch+1);
    	n=strlen(ch+1);
    	if(n&1){puts("0");return 0;}
    	for(int i=1;i<=n;i+=2)s[i]=ch[(i+1)/2];
    	reverse(&ch[1],&ch[n+1]);
    	for(int i=2;i<=n;i+=2)s[i]=ch[(i+1)/2];
    	ans[0]=1;
    	for(int i=1;i<=n;++i)
    	{
    		PT.extend(s[i]-97,i,s);
    		for(int k=PT.last;k;k=anc[k])
    		{
    			f[k]=ans[i-PT.t[anc[k]].len-diff[k]];
    			if(anc[k]!=PT.t[k].ff)
    				f[k]=(f[k]+f[PT.t[k].ff])%MOD;
    			if(!(i&1))ans[i]=(ans[i]+f[k])%MOD;
    		}
    	}
    	printf("%d
    ",ans[n]);
    	return 0;
    }
    
    
  • 相关阅读:
    const修饰成员函数 安静点
    友元 安静点
    空指针访问成员函数 安静点
    c++运算符重载 安静点
    类对象作为类成员 安静点
    C++对象模型和this指针 安静点
    c++初始化列表 安静点
    静态成员 安静点
    c++继承的基本语法 安静点
    架构漫谈阅读笔记1
  • 原文地址:https://www.cnblogs.com/cjyyb/p/8462865.html
Copyright © 2020-2023  润新知