• CF506E Mr. Kitayuta's Gift


    CF506E

    有个字符串,插入(n)个字符使得它变成回文串。

    问形成的不同的回文串的个数。

    (洛谷的题目大意有问题)

    (|s|le 10^9)

    (nle 200)


    神仙题。

    网上一堆博客讲得很清楚,那么这里就简单地复述一下。

    先考虑暴力。设(f_{i,l,r})表示回文串决定了前后(i)个字符,尽量给字符串匹配,剩下的字符串为([l,r])的方案数。

    (g_i)表示决定了前后(i)个字符,整个字符串匹配完了的方案数。

    1. (l+1=r and s_l=s_r or l=r)。转移至(g_{i+1})
    2. (s_l=s_r)。转移至(f_{i+1,l+1,r-1})(f_{i+1,l,r})
    3. (s_l eq s_r)。转移至(f_{i+1,l+1,r})(f_{i+1,l,r-1})(f_{i+1,l,r})

    (g_i)转移至(g_{i+1})

    系数自己补上。

    可以发现这是个有限状态自动机上匹配的过程:

    这里有(O(|s|^2))个点,直接矩阵乘法会爆炸。

    (s_l=s_r)的点为绿点,(s_l e s_r)的点为红点。

    从起点到终点有若干条路径,我们发现一条路径的贡献只跟经过的红点和绿点有关。

    假如一条路径上有(i)个红点,那么就有(lceilfrac{len-i}{2} ceil)个绿点。

    搞个类似于上面的dp来计算出每种路径出现了多少次。记作(g_i)

    把每个路径的贡献分别计算,时间复杂度(O(|s|^4lg n))

    还是过不去。把状态压成下面这样:

    这样就只有(frac{3}{2}|s|)个点。(g_i)挂在红点和绿点之间的横插边上。

    直接矩阵乘法就好了。

    如果是奇数的话这种方法还要修正一下:

    在确定中间的那个数的时候,如果从([i,i+1])转移过来,那就不合法。

    那就再用个矩阵乘法把这个贡献减掉。具体建图和上面类似,(g_i)只算([i,i+1])转移过来的贡献,终点没有自环。

    有个常数优化的小trick:

    由于矩阵乘法可以看做从小的编号向大的编号转移,于是如此枚举:j=i to n,k=i to j


    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 210
    #define ll long long
    #define mo 10007
    int n,m;
    char s[N];
    int f[N][N][N],g[N];
    void add(int &a,int b){a=(a+b)%mo;}
    int tot;
    struct Matrix{
    	int m[N*2][N*2];
    } T,T0,S;
    void multi(Matrix &a,Matrix &b){
    	static Matrix c;
    	for (int i=0;i<=tot;++i)
    		for (int j=i;j<=tot;++j){
    			ll sum=0;
    			for (int k=i;k<=j;++k)
    				sum+=a.m[i][k]*b.m[k][j];
    			c.m[i][j]=sum%mo;
    		}
    	memcpy(&a,&c,sizeof c);
    }
    void getpow(int n){
    	memset(&S,0,sizeof S);
    	for (int i=0;i<=tot;++i)
    		S.m[i][i]=1;
    	for (;n;n>>=1,multi(T,T))
    		if (n&1)
    			multi(S,T);
    }
    void build(bool er=1){
    	tot=n+((n-1)/2+1)+1-1;
    	memset(&T,0,sizeof T);
    	for (int i=0;i<n;++i){
    		if (i)
    			T.m[i][i]=24;
    		if (i<n-1)
    			T.m[i][i+1]=1;
    		int j=tot-((n-i-1)/2+1);
    		T.m[i][j]=g[i];
    	}
    	for (int i=n;i<tot;++i){
    		T.m[i][i]=25;
    		T.m[i][i+1]=1;
    	}
    	T.m[tot][tot]=(er?26:0);
    }
    int main(){
    	freopen("in.txt","r",stdin);
    //	freopen("out.txt","w",stdout);
    	scanf("%s%d",s+1,&m);
    	n=strlen(s+1);
    	f[1][n][0]=1;
    	for (int i=1;i<=n;++i)
    		for (int j=n;j>=i;--j)
    			for (int k=0;k<=n-(j-i+1);++k)
    				if (f[i][j][k]){
    					int v=f[i][j][k];
    					if (i+1==j && s[i]==s[j] || i==j)
    						add(g[k],v);
    					else if (s[i]==s[j])
    						add(f[i+1][j-1][k],v);
    					else{
    						add(f[i+1][j][k+1],v);
    						add(f[i][j-1][k+1],v);
    					}
    				}
    	build();
    	if (n+m&1){
    		getpow((n+m+1>>1)+1);
    		ll ans=S.m[0][tot];
    		for (int k=0;k<n;++k){
    			g[k]=0;
    			for (int i=1;i<n;++i)
    				if (s[i]==s[i+1])
    					add(g[k],f[i][i+1][k]);
    		}
    		build(0);
    		memset(&S,0,sizeof S);
    		getpow((n+m+1>>1)+1);
    		ans=(ans-S.m[0][tot]+mo)%mo;
    		printf("%d
    ",ans);
    	}
    	else{
    		getpow((n+m>>1)+1);
    		printf("%d
    ",S.m[0][tot]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    个人号微信机器人开发
    群控系统开发sdk服务端调用方法
    微信个人号scrm客服通信协议定义
    微信crm客服系统使用sdk定制开发(持续更新中!)
    微信客服crm系统接口定义(完善中)
    压测工具-ab
    设计模式之美学习-结构型-享元模式(二十五)
    设计模式之美学习-结构型-组合模式(二十四)
    设计模式之美学习-结构型-门面模式(二十三)
    设计模式之美学习-结构型-适配器模式(二十二)
  • 原文地址:https://www.cnblogs.com/jz-597/p/13660786.html
Copyright © 2020-2023  润新知