【CF506E】Mr. Kitayuta's Gift(自动机+矩乘)

点此看题面

  • 给定一个由小写字母组成的字符串(s),你需要插入恰好(n)个小写字母。
  • 问能得到多少种本质不同的回文串。
  • (|s|le200,nle10^9)

初步想法:暴力区间(DP)

一看(|s|)这么小就想搞个区间(DP),而(n)这么大显然就是要搞矩乘优化了。

考虑我们设(w_{i,j,k})表示原串两头分别匹配到(i,j)位置,且此时回文串左右各填了(k)个字符的方案数。

暴力的转移就是枚举接着在回文串左右同时填上什么字符。

如果这个字符和(s_i)(s_j)相同,则匹配成功,更新(i,j)(注意相同必须匹配防止算重),否则不操作。

基础的优化+分类讨论

然后发现一个基础的优化就是其实并不需要枚举填什么字符,因为我们不需要知道具体值,只需要知道它们是否等于(s_i)(s_j)就行了。

增设一个(t_k)表示回文串左右各填(k)个字符时完成匹配的方案数。

这里先假设回文串长度((n+|s|))为偶数,则转移分下面几类讨论。

  1. (s_i=s_j)(j-ile1)
    • (s_i),则完成匹配,(t_{k+1} exttt{+=}w_{i,j,k})
    • 填其他字符((25)种),(i,j)不变,(w_{i,j,k+1} exttt{+=}25 imes w_{i,j,k})
  2. (s_i=s_j)(j-i>1)
    • (s_i),则移动(i,j)(w_{i+1,j-1,k+1} exttt{+=}w_{i,j,k})
    • 填其他字符((25)种),(i,j)不变,(w_{i,j,k+1} exttt{+=}25 imes w_{i,j,k})
  3. (s_i ot=s_j)
    • (s_i),则移动(i)(w_{i+1,j,k+1} exttt{+=}w_{i,j,k})
    • (s_j),则移动(j)(w_{i,j-1,k+1} exttt{+=}w_{i,j,k})
    • 填其他字符((24)种),(i,j)不变,(w_{i,j,k+1} exttt{+=}24 imes w_{i,j,k})

还要注意,对于(t_k),由于无论加什么字符它依然完成匹配,故恒有转移(t_{k+1} exttt{+=}26 imes t_k)

此时如果矩乘优化掉(k),那么状态数是(O(|s|^2))的,因此复杂度是(O(|s|^6logn)),原地升天。

显然以我的水平也只能想到这一步了,剩下一些奇奇怪怪的优化还是得靠题解。

建自动机+自动机的压缩

考虑我们以((i,j))为状态,加上一个超级汇(最终态(t)),根据上面的转移方程建出自动机(自动机上的边权就是转移时的系数),但它的节点规模依旧是(O(|s|^2))的。

然后就要开始玄学操作了。

我们仔细观察之前的转移方程,发现一个点只有仅仅几条出边,却有一堆自环。而自环个数只可能是(24)(25)(26),其中(26)仅存在于最终态。

对于一条从起始状态((1,|s|))到最终态(t)的路径,假设经过了(x)个有(24)个自环的点和(y)个有(25)个自环的点,显然需要满足(y=lceilfrac{|s|-x}2 ceil)。(因为从有(24)个自环的点走出只有一个端点会变,从(25)个自环的点走出两个端点都会变,而总共变化次数是(|s|)(|s|+1)

考虑我们调整节点顺序,把(x)(24)个自环的点移到前面,(y)(25)个自环的点放在后面,发现在这条路径上的每一种走法和原路径上的每一种走法可以一一对应,两条路径等价。

更进一步,发现所有(x,y)相同的路径都是等价的!也就是所有路径完全可以根据(x)分成(|s|)种!

这样一来自动机的压缩就很显然了,建出(|s|)个红点((24)个自环的点)和(lceilfrac{|s|}2 ceil)个绿点((25)个自环的点)以及一个超级源和一个超级汇,连边方式如下:

  • 超级源向左起第一个红点连一条边,右起第一个绿点向超级汇连一条边,超级汇有一个边权为(26)的自环。
  • 每个红点有一个边权为(24)的自环,每个绿点有一个边权为(25)的自环,相邻红点、相邻绿点之间连一条边。
  • 对于左起第(i)个红点,向右起第(lceilfrac{|s|-i}2 ceil)个绿点连一条边,边权为起始状态((1,|s|))到最终态(t)(i)个红点的方案数

这样一来点数就是(O(|s|))的,再只要矩乘优化就解决了。

现在的遗留问题就是起始状态((1,|s|))到最终态(t)(i)个红点的方案数。

显然仿照之前的暴力区间(DP),用(f_{i,j,k})表示匹配到(i,j),有(k)个红点的方案数,并用(g_k)表示匹配完成时有(k)个红点的方案数,(O(|s|^3))暴力转移一下就行了。

回文串长度为奇数的附加处理

注意,先前的一切都是基于回文串长度为偶数处理的,还要考虑回文串长度为奇数怎么办。

发现奇数相对偶数的变化其实就是在还剩两个字符时无法转移到最终态。

因此我们在原图上稍作修改,改为这种情况下的方案数,并删去最终态的自环,然后求出这部分的方案数并从总答案数中减去即可。

代码:(O(|s|^3logn))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define L 200
#define X 10007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,l,f[L+5][L+5][L+5],g[L+5];char s[L+5];
namespace Mat//矩乘优化
{
	int tot;struct M
	{
		#define SZ 300
		int a[SZ+5][SZ+5];I M() {memset(a,0,sizeof(a));}
		I int* operator [] (CI x) {return a[x];}I Con int* operator [] (CI x) Con {return a[x];}
		I M operator * (Con M& o) Con//矩乘
		{
			M t;for(RI i=0,j,k;i<=tot;++i) for(j=i;j<=tot;++j)
				for(k=i;k<=j;++k) t[i][j]=(a[i][k]*o[k][j]+t[i][j])%X;return t;
		}
	}S,U;
	I M Pow(RI y) {W(y) y&1&&(S=S*U,0),U=U*U,y>>=1;return S;}//矩阵快速幂
	I int Solve(CI tg)
	{
		S=U=M(),S[0][1]=1,S[0][tot-(l+1>>1)]=g[0],tg&&(U[tot][tot]=26);//初始化
		RI i;for(i=1;i<=l;++i) U[i][i]=24,U[i][tot-(l-i+1>>1)]=g[i],i^l&&(U[i][i+1]=1);//自环;红绿;红红
		for(i=l+1;i^tot;++i) U[i][i]=25,U[i][i+1]=1;return Pow(n+l+1>>1)[0][tot];//自环;绿绿 返回答案为超级源到超级汇的方案数
	}
}
int main()
{
	RI i,j,k,t;scanf("%s%d",s+1,&n),l=strlen(s+1);
	for(f[1][l][0]=1,t=l;t;--t) for(i=1,j=t;j<=l;++i,++j) for(k=0;k<=l;++k)//暴力区间DP
		s[i]^s[j]?(Inc(f[i+1][j][k+1],f[i][j][k]),Inc(f[i][j-1][k+1],f[i][j][k]))//绿点
		:(i+1<j?Inc(f[i+1][j-1][k],f[i][j][k]):Inc(g[k],f[i][j][k]));//红点
	if(Mat::tot=l+(l+1>>1)+1,t=Mat::Solve(1),!((n+l)&1)) return printf("%d
",t),0;//如果为偶数
	for(memset(g,0,sizeof(g)),i=1;i^l;++i) if(s[i]==s[i+1]) for(k=0;k<=l;++k) Inc(g[k],f[i][i+1][k]);//如果为奇数
	return printf("%d
",(t-Mat::Solve(0)+X)%X),0;//减去非法方案数
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF506E.html