其他分享
首页 > 其他分享> > CF506E Mr. Kitayuta's Gift

CF506E Mr. Kitayuta's Gift

作者:互联网

一、题目

点此看题

二、解法

使我深受洗礼的一道题,是既有思维难度又有代码难度不可多得的好题

先考虑偶回文串吧!首先考虑如何计数,题目都告诉你只关心最终状态,我们直接对最终状态计数。考虑枚举法确定原来字符在最终序列的位置,那么可以简单计数来确定方案。

用 \(dp\) 优化这个过程,设 \(f_{i,l,r}\) 为考虑最终序列的前 \(i\) 个和后 \(i\) 个字符,原串还剩下区间 \([l,r]\) 没有确定位置的方案数,设 \(g_i\) 表示考虑最终序列的前 \(i\) 个和后 \(i\) 个字符,原串已经用完的方案数。

为了不算重我们需要把某种方案只使用某种方式表示,就算它可以通过多种"方式"表示出来,我们也规定一种方式来储存它:

若 \(s_l=s_r,r-l\leq 1\),则可以直接到终状态,\(f\) 向 \(g\) 转移,还可以不同地填字符对(\(25\) 种):

\[g_{i+1}\leftarrow f_{i,l,r} \]

\[f_{i+1,l,r}\leftarrow 25\cdot f_{i,l,r} \]

若 \(s_l=s_r,r-l\geq 2\),则可以把两边取出来配对,也可以不同地填字符对(\(25\) 种):

\[f_{i+1,l+1,r-1}\leftarrow f_{i,l,r} \]

\[f_{i+1,l,r}\leftarrow 25\cdot f_{i,l,r} \]

若 \(s_l\not=s_r\),则可以取出一边再拿个任意字符来配对,也可以不同地填字符对(\(24\) 种):

\[f_{i+1,l+1,r}\leftarrow f_{i,l,r} \]

\[f_{i+1,l,r-1}\leftarrow f_{i,l,r} \]

\[f_{i+1,l,r}\leftarrow 24\cdot f_{i,l,r} \]

最后是 \(g\) 的转移,可以有 \(26\) 种任意的填法:\(g_{i+1}\leftarrow 26\cdot g_i\)


上面的转移可以直接套矩阵乘法,时间复杂度 \(O(m^6\log n)\)

请容许我盗波图,继续优化我们可以画出转移的图像,下图的红点表示左右端点消减 \(1\) 的转移点,绿点表示左右端点消减 \(2\) 的转移点,答案就是这个图上起点到终点的路径方案数:

img

因为原串大小固定,如果一条路径上有 \(k\) 个红点,那么就必须有 \(\lfloor\frac{m-k}{2}\rfloor\) 个绿点。而路径的方案数是和自环紧密相关的,也就是和经过的红点和绿点个数相关的,而和权值只有 \(1\) 的边没有太大关系,所以说本质不同的路径条数只有 \(O(m)\) 条。

所以可以统计每种路径的出现次数,对它单独矩乘,简单乘法原理就可以算出答案,时间复杂度 \(O(m^4\log m)\),统计路径的那一步可以用记忆化搜索,设 \(h_{i,l,r}\) 为 \(i\) 个绿点,考虑区间 \([l,r]\) 的方案数。

进一步优化可以考虑整体 \(dp\),因为它们的转移规则是相同的,所以我们建在一张图上:

img

其中从后往前第 \(i\) 个红点\(/\)绿点就表示路径上还需要走 \(i\) 个红点\(/\)绿点,连接红点和绿点之间的边就设置成这种路径的条数即可,注意没有红点需要初始化在绿点上,用矩阵加速跑这张图时间复杂度 \(O(m^3\log n)\)

如果最后是奇回文串,那么可以把不合法的情况减去,也就是最后一步通过长度为 \(2\) 的绿点转移到终点的情况,那么我们去掉终点的自环,再只保留到这些绿点的路径,同样跑一遍矩乘就可以出答案。

实现小细节:由于我们建出的图是上三角(只会由编号小的转移到编号大的),所以做乘法的时候也可以只乘上三角,据说常数直接小到 \(\frac{1}{6}\)

三、总结

本题我所缺失的就是那步关键的去重处理,方案如何表示很重要,如果只关心最终方案,那么过程中有多种方法可以强制通过某一种方法达到最终方案。

其他设计到的技巧:设计图论模型直观表示问题;等价类的划分;转移方式相同使用整体 \(dp\)

本题的模型其实也可以称为有限状态自动机,字符串问题可以朝这个方向思考一下。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 205;
const int MOD = 1e4+7;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,g[M][M],f[M],h[M][M][M];char s[M];
int ceil(int x)
{
	return (x>>1)+(x&1);
}
int dfs(int i,int l,int r)
{
	if(i<0 || l>r) return 0;
	if(h[i][l][r]!=-1) return h[i][l][r];
	int &o=h[i][l][r];o=0;
	if(l==1 && r==m) return o=!i;
	if(l>1 && r<m && s[l-1]==s[r+1])
		o=(o+dfs(i,l-1,r+1))%MOD;
	if(l>1 && s[l-1]!=s[r])
		o=(o+dfs(i-1,l-1,r))%MOD;
	if(r<m && s[l]!=s[r+1])
		o=(o+dfs(i-1,l,r+1))%MOD;
	return o;
}
void qkpow(int b)
{
	while(b>0)
	{
		if(b&1)
		{
			int a[M]={};
			for(int i=1;i<=k;i++)
			for(int j=1;j<=k;j++)
				a[i]=(a[i]+f[j]*g[j][i])%MOD;
			swap(a,f);
		}
		int a[M][M]={};
		for(int i=1;i<=k;i++)
		for(int j=i;j<=k;j++)
		for(int l=j;l<=k;l++)
			a[i][l]=(a[i][l]+g[i][j]*g[j][l])%MOD;
		swap(a,g);
		b>>=1;
	}
}
signed main()
{
	scanf("%s",s+1),m=strlen(s+1);n=read();
	memset(h,-1,sizeof h);k=m+ceil(m);
	//[1,m) red ; [m,k) green ; k end
	for(int i=0;i<m;i++)
	{
		int c=0;
		for(int j=1;j<=m;j++)
		{
			c=(c+dfs(i,j,j))%MOD;
			if(j<m && s[j]==s[j+1])
				c=(c+dfs(i,j,j+1))%MOD;
		}
		if(!i)
		{
			f[m]=c;g[k][k]=26;
			for(int j=m;j<k;j++)
				g[j][j+1]=1,g[j][j]=25;
		}
		else
		{
			g[i][k-ceil(m-i)]=c;g[i][i]=24;
			if(i>1) g[i-1][i]=1;
			else f[i]=1;
		}
	}
	int F[M]={},G[M][M]={};
	memcpy(F,f,sizeof F);
	memcpy(G,g,sizeof G);
	qkpow(ceil(n+m));
	if((n+m)%2==0)//even length
	{
		printf("%d\n",f[k]);
		return 0;
	}
	int ans=f[k];
	memcpy(f,F,sizeof f);
	memcpy(g,G,sizeof g);
	//build the new graph,containing illegal roads
	for(int i=0;i<m;i++)
	{
		int c=0;
		for(int j=1;j<m;j++) if(s[j]==s[j+1])
			c=(c+dfs(i,j,j+1))%MOD;
		if(i) g[i][k-ceil(m-i)]=c;
		else f[m]=c,g[k][k]=0;
	}
	qkpow(ceil(n+m));
	printf("%d\n",(ans-f[k]+MOD)%MOD);
}

标签:Gift,绿点,leftarrow,int,可以,路径,CF506E,Kitayuta,转移
来源: https://www.cnblogs.com/C202044zxy/p/15505807.html