• HNOI2008 GT考试 和 Beijing2017 魔法咒语


    GT考试

    阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的数字。他的不吉利数学A1A2...Am(0<=Ai<=9)有M位,不出现是指X1X2...Xn中没有恰好一段等于A1A2...Am. A1和X1可以为0。

    分析

    很好想的AC自动机上dp,用(f[i][j])表示放了(i)位在节点(j)的方案数。

    [f[0][0]=1\ ans=sum_{j=0}^{m-1}f[n][j]\ f[i][j]=sum_{k ightarrow j}f[i-1][k] ]

    然后这个式子是(O(10 * N * M))的。

    考虑矩阵加速,很像BZOJ4861 [Beijing2017]魔法咒语,于是自己写了写,一遍AC了。

    并且我写的还是AC自动机,所以禁忌串完全可以弄多一点。

    时间复杂度(O(M^3 log_2 N)),是212603.39807279119026370044348732,随便跑,怪不得这题总时限是1s。

    int mod;
    
    int add(int x,int y)
    {
    	x+=y;
    	return x>=mod?x-mod:x;
    }
    
    int mul(int x,int y)
    {
    	return (ll)x*y%mod;
    }
    
    co int N=21;
    namespace AC
    {
    	int tot;
    	int ch[N][10],fail[N],val[N];
    	
    	void insert(char s[],int n)
    	{
    		int u=0;
    		for(int i=0;i<n;++i)
    		{
    			int k=s[i]-'0';
    			if(!ch[u][k])
    				ch[u][k]=++tot;
    			u=ch[u][k];
    		}
    		val[u]=1;
    	}
    	
    	void getfail()
    	{
    		std::queue<int>Q;
    		for(int i=0;i<10;++i)	
    			if(ch[0][i])
    				Q.push(ch[0][i]);
    		while(Q.size())
    		{
    			int u=Q.front();Q.pop();
    			val[u]|=val[fail[u]];
    			for(int i=0;i<10;++i)
    			{
    				if(ch[u][i])
    				{
    					fail[ch[u][i]]=ch[fail[u]][i];
    					Q.push(ch[u][i]);
    				}
    				else
    					ch[u][i]=ch[fail[u]][i];
    			}
    		}
    	}
    	
    	int ANS[N][N],A[N][N],c[N][N];
    	
    	void mul(int a[N][N],int b[N][N])
    	{
    		for(int k=0;k<=tot;++k)
    			for(int i=0;i<=tot;++i)if(a[i][k])
    				for(int j=0;j<=tot;++j)if(b[k][j])
    					c[i][j]=add(c[i][j],::mul(a[i][k],b[k][j]));
    		for(int i=0;i<=tot;++i)
    			for(int j=0;j<=tot;++j)
    				a[i][j]=c[i][j],c[i][j]=0;
    	}
    	
    	void solve(int n)
    	{
    		for(int i=0;i<=tot;++i)if(!val[i])
    			for(int j=0;j<10;++j)if(!val[ch[i][j]])
    				++A[i][ch[i][j]];
    		ANS[0][0]=1;
    		while(n)
    		{
    			if(n&1)
    				mul(ANS,A);
    			mul(A,A);
    			n>>=1;
    		}
    		int ans=0;
    		for(int i=0;i<=tot;++i)if(!val[i])
    			ans=add(ans,ANS[0][i]);
    		printf("%d
    ",ans);
    	}
    }
    char buf[N];
    
    int main()
    {
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
    	int n,m;
    	read(n),read(m),read(mod);
    	scanf("%s",buf);
    	AC::insert(buf,m);
    	AC::getfail();
    	AC::solve(n);
    	return 0;
    }
    

    魔法咒语

    给定n个原串和m个禁忌串,要求用原串集合能拼出的不含禁忌串且长度为L的串的数量模 1,000,000,007的结果。(60%)n,m<=50,L<=100。(40%)原串长度为1或2,L<=10^18。

    ONION_CYC的题解

    其实题意的数据范围不太清晰,反正开200个点就足够了。

    因为要匹配禁忌串,所以对禁忌串集合建立AC自动机,标记禁忌串结尾节点,以及下传到所有能fail到的点(这些点访问到都相当于匹配了禁忌串)。

    (f[i][j])表示匹配到节点(i),长度为(j)的串的数量,先预处理(a[i][j])表示节点 (i) 匹配第 (j) 个原串到达的节点编号,那么就有:

    [f [ a[i][j] ] [ L+len[j] ] += f [ i ] [ L ] ]

    以上就是60%数据的做法,对于40%的数据使用矩阵快速幂。

    假设原串长度均为1,那么DP的转移如下:

    [f[i][L]=sum_{j→i}f[j][L−1] ]

    这很容易用一个长度为第一维大小(AC自动机节点数)的矩阵维护转移,第L个列向量就是f[i][L]。

    如果原串长度有2,那么再记录L-1即可。

    答案矩阵如下:

    [left[ egin{matrix} f[0][L] & f[0][L-1] & dots & f[i][L] & f[i][L-1] & dots & f[tot][L] & f[tot][L-1] end{matrix} ight] ]

    构造转移矩阵,考虑转移矩阵的意义,第(i)行的元素的含义是由(f[i][dots])转移,第(j)列的元素的含义是转移到(f[j][dots])。转移矩阵的两个下标(A[i][j])可以看成从(i)转移到(j)。于是不难构造。

    时间复杂度

    对于60%的数据,(O(L * ML *N)),是25000000。中间的(ML)是因为禁忌串长似乎与(L)同阶。
    对于另40%的数据,(Oleft((2M)^3 log_2 L ight)),是26575424.759098898782962555435915。

    关于矩阵的问题

    原作者的矩阵写法跟我的是反着的,导致WA了几次。

    [(AB)^T=B^TA^T ]

    如果将矩阵转置,左右乘也要倒。

    另外算快速幂的时候可以边倍增边算答案。

    co int maxn=5001,mod=1e9+7;
    int n,m,L;
    char s[51][101],buf[101]; // 101:possible string length
    int len[51];
    namespace AC
    {
    	int tot;
    	int ch[maxn][26],val[maxn],fail[maxn],a[maxn][101];
    	
    	void insert(char s[],int n)
    	{
    		int u=0;
    		for(int i=0;i<n;++i)
    		{
    			int k=s[i]-'a';
    			if(!ch[u][k])
    				ch[u][k]=++tot;
    			u=ch[u][k];
    		}
    		val[u]=1;
    	}
    	
    	void build()
    	{
    		std::queue<int>Q;
    		for(int i=0;i<26;++i)
    			if(ch[0][i])
    				Q.push(ch[0][i]);
    		while(Q.size())
    		{
    			int u=Q.front();Q.pop();
    			for(int i=0;i<26;++i)
    			{
    				if(ch[u][i])
    				{
    					fail[ch[u][i]]=ch[fail[u]][i];
    					Q.push(ch[u][i]);
    					val[ch[u][i]]|=val[fail[ch[u][i]]];
    				}
    				else
    					ch[u][i]=ch[fail[u]][i];
    			}
    		}
    		memset(a,-1,sizeof a);
    		for(int k=1;k<=n;++k)
    			for(int i=0;i<=tot;++i)
    			{
    				int u=i;
    				for(int j=0;j<len[k];++j)
    				{
    					if(!val[u])
    						u=ch[u][s[k][j]-'a'];
    					else break;
    				}
    				if(!val[u])
    					a[i][k]=u;
    			}
    	}
    }
    
    int add(int x,int y)
    {
    	x+=y;
    	return x>=mod?x-mod:x;
    }
    
    int mul(int x,int y)
    {
    	return (ll)x*y%mod;
    }
    
    namespace T1
    {
    	using namespace AC;
    	int f[maxn][101];
    	
    	void solve()
    	{
    		f[0][0]=1;
    		for(int l=0;l<L;++l)
    			for(int i=0;i<=tot;++i)if(f[i][l])
    				for(int j=1;j<=n;++j)if(~a[i][j]&&l+len[j]<=L)
    					f[a[i][j]][l+len[j]]=add(f[a[i][j]][l+len[j]],f[i][l]);
    		int ans=0;
    		for(int i=0;i<=tot;++i)
    			if(f[i][L]&&!val[i])
    				ans=add(ans,f[i][L]);
    		printf("%d
    ",ans);
    	}
    }
    
    namespace T2
    {
    	using namespace AC;
    	co int maxn=101;
    	int N,A[maxn*2][maxn*2],ANS[maxn*2][maxn*2],c[maxn*2][maxn*2];
    	
    	void mul(int a[maxn*2][maxn*2],int b[maxn*2][maxn*2])
    	{
    		for(int k=0;k<=N;++k)
    			for(int i=0;i<=N;++i)if(a[i][k])
    				for(int j=0;j<=N;++j)if(b[k][j])
    					c[i][j]=add(c[i][j],::mul(a[i][k],b[k][j]));
    		for(int i=0;i<=N;++i)
    			for(int j=0;j<=N;++j)
    				a[i][j]=c[i][j],c[i][j]=0;
        }
    	
    	void solve()
    	{
    		N=tot*2+1;
    		for(int i=0;i<=tot;++i)
    		{
    			for(int j=1;j<=n;++j)if(~a[i][j])
    			{
    				if(len[j]==1)
    					++A[i*2][a[i][j]*2];
    				else
    					++A[i*2+1][a[i][j]*2];
    			}
    			A[i*2][i*2+1]=1;
    		}
    		ANS[0][0]=1;
    		while(L)
    		{
    			if(L&1)
    				mul(ANS,A);
    			mul(A,A);
    			L>>=1;
    		}
    		int ans=0;
    		for(int i=0;i<=tot;++i)if(!val[i])
    			ans=add(ans,ANS[0][i*2]);
    		printf("%d
    ",ans);
    	}
    }
    
    int main()
    {
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
    	read(n),read(m),read(L);
    	for(int i=1;i<=n;++i)
    	{
    		scanf("%s",s[i]);
    		len[i]=strlen(s[i]);
    	}
    	for(int i=1;i<=m;++i)
    	{
    		scanf("%s",buf);
    		AC::insert(buf,strlen(buf));
    	}
    	AC::build();
    	if(L<=100)
    		T1::solve();
    	else
    		T2::solve();
    	return 0;
    }
    
  • 相关阅读:
    Linux 学习 step by step (2)
    公共建筑能耗监测平台的GPRS通讯服务器的开发方法分享
    幸福框架:可扩展的、动态的、万能的 编号生成器
    C++ Data Member内存布局
    .NET程序集强命名删除与再签名技术 源代码剖析
    hdu 2191(多重背包)
    五种情况下会刷新控件状态(刷新所有子FWinControls的显示)——从DFM读取数据时、新增加子控件时、重新创建当前控件的句柄时、设置父控件时、显示状态被改变时
    终于懂了:Delphi消息的Result域出现的原因——要代替回调函数的返回值!(MakeObjectInstance不会帮助处理(接收)消息回调函数的返回值)
    Firemonkey实现Mac OS程序中内嵌浏览器的功能(自己动手翻译,调用苹果提供的webkit框架)
    感悟:市场经济看得就是主观能动性,有则富贵可及,无则无限趋于零
  • 原文地址:https://www.cnblogs.com/autoint/p/10322870.html
Copyright © 2020-2023  润新知