Mr. Kitayuta's Gift,CF506E,有限状态自动机+分析状态后矩阵快速幂

正题

      Protal

      这题很有难度。

      首先可以想到一个简单的方程,用f[i][j][k]表示手动加了k个将两边消掉,中间剩下[l,r]的方案数。

      可以想到,为了避免重复,当最后的串中存在bbxxbb的时候,我们把手动加的bb都放在最靠近中间的位置。

      那么就可以构造一个有限状态自动机,有三种节点,一种是n24(有24种自环),一种是n25,一种是n26。

      一个节点为n24当且仅当它是一个两端不相等的子串。

      一个节点为n25当且仅当它是一个两端相等的子串。

      一个节点为n26当且仅当它是一个空串。

      如图,就可以构造出一个有限状态自动机。

      可以发现,n24(红点)出度为2,n25(绿点)出度为1。


      为什么我们一遇到两边相等就直接删除两端的字符而不是删除一端然后剩下另一端?

      避免重复,如果只删除一端就会发现最后的字符串和直接删除两端是一样的东西,而且当我们直接删除两端的时候,会有更多“方案”供我们选择,而只删除一端就只有唯一一种方案。

      有点绕,举个例子aa,假若要进行两次操作,使得最后的串为偶数串,那么我们直接删掉aa,在下一步可以选择往中间插入aa,...,zz,但是如果我们只删除一端,第二步的操作就只能删除最后一个字符,只有一种选择就是aaaa。


      解释完了这个,这个有限状态自动机都可以自己建出来了,实际上我们可以发现根本不用那么多点。

      又可以发现,当红点个数确认下来后,绿点个数是唯一确定的。

      总共只有|s|种红点个数不同的路径,而对于红点个数相同的两条路径,得出来得方案数是一致的,所以我们可以先求出各种路径的条数,再对每一条路径跑一次矩阵快速幂,时间复杂度来到四次方,不可取。

       实际上,我们可以通过建图来使得n条路径变成一张n个点的图。

       怎么变?

       第一层是|s|个红点,编号后每一个向下一个连一条权为1的边,向自己连一条权为24的边。

       第二层是\left \lceil \frac{|s|+1}{2} \right \rceil个绿点,编号后每一个乡辖一个连一条权为1的边,向自己连一条权为25的边。

       第三层是一个终点,向自己连一条权为26的边。

       第i个红点向倒数\left \lceil \frac{|s|-i+1}{2} \right \rceil连一条权值为”红点个数为i的路径条数“的边,第二层的最后一个节点向第三层连一条权为1的边。

       发现这样两两路径不会互相影响。这样建图十分巧妙,请细心理解。

       对于|s|+n为奇数的情况,要将xx作为最后一步的情况剔除掉,就得解了。

#include<bits/stdc++.h>
using namespace std;

const int N=210;
char s[N];
int f[N][N][N],g[N];
const int mod=10007;
int n,m,maxn;
struct Matrix{
	int A[N<<1][N<<1];
	Matrix operator*(const Matrix q)const{
		Matrix p;
		for(int i=1;i<=maxn;i++)
			for(int j=1;j<=maxn;j++){
				p.A[i][j]=0;
				for(int k=1;k<=maxn;k++)
					p.A[i][j]+=A[i][k]*q.A[k][j]%mod;
				p.A[i][j]%=mod;
			}
		return p;
	}
}trs;

void solve(Matrix&x,int t){
	Matrix tot;
	memset(tot.A,0,sizeof(tot.A));
	for(int i=1;i<=maxn;i++) tot.A[i][i]=1;
	while(t){
		if(t&1) tot=tot*x;
		x=x*x;
		t/=2;
	}
	x=tot;
}

void inc(int&x,int y){x+=y,x>=mod?x-=mod:0;}

int main(){
	scanf("%s",s+1);n=strlen(s+1);
	scanf("%d",&m);f[1][n][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=n;j>=i;j--){
			if(s[i]==s[j]){
				for(int k=0;k<=i-1+n-j;k++){
					if(i+2<=j) inc(f[i+1][j-1][k],f[i][j][k]);
					else inc(g[k],f[i][j][k]);
				}
			}
			else{
				for(int k=0;k<=i-1+n-j;k++){
					inc(f[i+1][j][k+1],f[i][j][k]);
					inc(f[i][j-1][k+1],f[i][j][k]);
				}
			}
		}
	maxn=n+(n+1)/2+1;
	for(int i=1;i<=n;i++){
		if(i<n) trs.A[i][i+1]=1;
		trs.A[i][i]=24;trs.A[i][maxn-(n-i+1)/2]=g[i];
	}
	for(int i=n+1;i<maxn;i++) trs.A[i][i]=25,trs.A[i][i+1]=1;
	trs.A[maxn][maxn]=26;
	solve(trs,(n+m+1)>>1);
	int ans=trs.A[1][maxn]+trs.A[n+1][maxn]*g[0]%mod;
	if((n+m)&1){
		for(int i=0;i<=n;i++) g[i]=0;
		for(int i=1;i<n;i++) if(s[i]==s[i+1]){
			for(int k=0;k<=n-2;k++) inc(g[k],f[i][i+1][k]);
		}
		memset(trs.A,0,sizeof(trs.A));
		for(int i=1;i<=n;i++){
			if(i<n) trs.A[i][i+1]=1;
			trs.A[i][i]=24;trs.A[i][maxn-(n-i+1)/2]=g[i];
		}
		for(int i=n+1;i<maxn;i++) trs.A[i][i]=25,trs.A[i][i+1]=1;
		solve(trs,(n+m+1)>>1);
		ans=ans-trs.A[1][maxn]-trs.A[n+1][maxn]*g[0]%mod;
	}
	printf("%d\n",(ans%mod+mod)%mod);
}

     

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值