poj1625 Censored!(AC自动机+矩阵快速幂/dp+大数)

  • Post author:
  • Post category:其他


题目

n<=50,代表字母表里最多n个字母,

然后p个被禁止的串,

问串长不超过m有多少合法串

思路来源


https://www.cnblogs.com/kuangbin/p/3159954.html

题解

其实和前面的几道题基本一样,

就是需要用大数,然后调调参就过了

矩阵快速幂可以用dp替代,

dp[i][j]代表串长不超过i的以状态j结尾的方案数,

由于必须从根节点root=状态0出发,

所以预处理dp[0][0]=1,dp[0][其他]=0,

在AC自动机上走m条边,即再走m个节点即可

然后由于大数空间比较吃紧,

开个滚动数组优化一下

注意更新dp[i][j]之前先给清零,

不然里面是dp[i-2][j]的值

心得

哎又是一题debug1h系列,凉凉

以后串长还是开大一点吧

gets字符串s[],本来估的上限55,结果re了

然后开到65才过的……

其实大可不必这么抠空间……

开个几百也没啥问题……

但的确想知道为什么略高于理论上界不行,很迷啊

kuangbin老师的大数板子不错,回头整理一下QAQ

自己的都需要init,不能直接大数和数加或乘,比较鸡肋

这么试下界的我也是非常菜了……

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
struct node
{
  int fail,Next[55];    
  bool tag;
}trie[105];
char s[65];
int id[55],cnt;
int que[105];//建ac自动机的队列 
int root,tot;//根的编号 节点编号 
ull ans;
int n,m,p;
struct Matrix
{
	ull a[110][110];
	int r;
	Matrix():r(105){
		memset(a,0,sizeof a);
	}
	Matrix(int n):r(n)//考虑0次的单位矩阵 
	{
		for(int i=0;i<n;++i)
		{
			for(int j=0;j<n;++j)
			{
				a[i][j]=0;
			}
		}
	}
	Matrix operator*(const Matrix &b)const
	{
		Matrix tmp(r);
		for(int i=0;i<r;++i)
		{
			for(int j=0;j<r;++j)
			{
				for(int k=0;k<r;++k)
				{
					tmp.a[i][j]+=a[i][k]*b.a[k][j];
				}
			}
		}
		return tmp;
	}
}M;
/* 
Matrix p(1),q(2),N;
Matrix modpow(Matrix x,ll n)
{
	Matrix p(x.r);
	for(int i=0;i<p.r;++i)
	p.a[i][i]=1;
	while(n)
	{
		if(n&1)p=p*x;
		x=x*x,n/=2;
	}
	return p;
}
Matrix S(Matrix a,ll k)
{
	Matrix b(2*a.r);
	for(int i=0;i<a.r;++i)
	{
		for(int j=0;j<a.r;++j)
		{
		 b.a[i][j]=a.a[i][j];
	    }
	    b.a[a.r+i][i]=b.a[a.r+i][a.r+i]=1;
	}
	b=modpow(b,k+1);
	return b;
}*/
void insert(int r,char *s)//(root,串),trie树插入 
{
   int len=strlen(s);
   for(int i=0;i<len;i++)
   {
      if(trie[r].Next[id[s[i]]]==0)
	  {
	   trie[r].Next[id[s[i]]]=++tot;
	   trie[trie[r].Next[id[s[i]]]].tag=0;
      }
      r=trie[r].Next[id[s[i]]];
   }     
   trie[r].tag=1;//是病毒的结尾 
   //if(trie[r].tag)printf("%d\n",r);
}
void build(int r,int N)//建ac自动机 
{
  int head=0,tail=0;
  trie[r].fail=r;//root自己连自己 
  que[tail++]=r;
  while(head<tail)
  {
    r=que[head++];
    if(trie[trie[r].fail].tag)trie[r].tag=1;//后缀的最后一个字母是病毒 那么自己也是 importance1
    for(int i=0;i<N;i++)
    {
      int ch=trie[r].Next[i],failp;
      if(ch)
      {
        que[tail++]=ch;
        for(failp=trie[r].fail;failp!=root&&trie[failp].Next[i]==0;failp=trie[failp].fail);//向前回跳 
        if(trie[failp].Next[i]==0||trie[failp].Next[i]==ch)trie[ch].fail=failp;//接到根或者自己 
		else trie[ch].fail=trie[failp].Next[i];//可以后接 
      }
      else trie[r].Next[i]=trie[trie[r].fail].Next[i];//和fail续一个一模一样的后继 importance2
    }
  }     
}
void debug()
{
	for(int i=0;i<M.r;++i)
    {
    	for(int j=0;j<M.r;++j)
    	printf("%llu ",M.a[i][j]);
    	puts("");
    }
}
void buildMatrix(int N)
{
	 M=Matrix(tot);
	 for(int i=1;i<=tot;++i)
	 {
	   //if(trie[i].tag) printf("%d ",i);
	   for(int j=0;j<N;++j)
	   {
	  	 if(!trie[i].tag&&!trie[trie[i].Next[j]].tag)
	  	 M.a[i-1][max(trie[i].Next[j]-1,0)]++;
	   }  
	 } 
	 //debug();
}
struct BigInt
{
    const static int mod=10000;
    const static int LEN=4;
    int a[800],len;
    BigInt()
    {
        memset(a,0,sizeof(a));
        len=1;
    }
    void init(int x)
    {
        memset(a,0,sizeof(a));
        len=0;
        do//四位一存 如123456789存为 6789 2345 1 
        {
            a[len++]=x%mod;
            x/=mod;
        }while(x);
    }
    void Init(const char s[])//重点 123 4567
    {
        memset(a,0,sizeof(a));
        int l=strlen(s),res=0;
        len=l/LEN;
        if(l%LEN)len++;//l/LEN向上取整 len=2 表明需要两个字节能存下 
        for(int i=l-1;i>=0;i-=LEN)
        {
            int t=0,k=max(i-LEN+1,0);//k是找到当前字节的最高位 回退len-1长 防越界 
            for(int j=k;j<=i;j++)t=t*10+s[j]-'0';//从4开始 t临时存储4567 
            a[res++]=t;//将低位存入 
        }
    } 
    int Compare(const BigInt &b)//位多的大 
    {
        if(len<b.len)return -1;
        if(len>b.len)return 1;
        for(int i=len-1;i>=0;i--)//从高位比较 
            if(a[i]<b.a[i])return -1;
            else if(a[i]>b.a[i])return 1;
        return 0;//完全相等的情况 
    }
    BigInt operator +(const BigInt &b)const
    {
        BigInt ans;
        ans.len=max(len,b.len);
        for(int i=0;i<=ans.len;i++)ans.a[i]=0;
        for(int i=0;i<ans.len;i++)
        {
            ans.a[i]+=((i<len)?a[i]:0)+((i<b.len)?b.a[i]:0);//防止越位现象 
            ans.a[i+1]+=ans.a[i]/mod;//向高位进位 
            ans.a[i]%=mod;
        }
        if(ans.a[ans.len]>0)ans.len++;//产生了9999+9999=9998 1的数组高位进位 
        return ans;
    }
    BigInt operator -(const BigInt &b)const//确保被减数大 差为正 
    {
        BigInt ans;
        ans.len=len;
        int k=0;
        for(int i=0;i<ans.len;i++)
        {
            ans.a[i]=a[i]+k-b.a[i];
            if(ans.a[i]<0)ans.a[i]+=mod,k=-1;//向a[i]高位借10000,k=-1下轮生效 
            else k=0;          
        }
        while(ans.a[ans.len-1]==0&&ans.len>1)ans.len--;//把前缀0去掉 如果ans.len=1时说明a=b差为0 
        return ans;
    }
    BigInt operator *(const BigInt &b)const
    {
        BigInt ans;
        for(int i=0;i<len;i++)
        {
            int k=0;
            for(int j=0;j<b.len;j++)
            {
                int temp=a[i]*b.a[j]+ans.a[i+j]+k;//模拟小学生乘法 i*j加到i+j上去 k为低位来的进位 
                ans.a[i+j]=temp%mod;
                k=temp/mod;//k为向高位进的位 下一轮生效 
            }
            if(k!=0)ans.a[i+b.len]=k;//高位进位 99*99=9801 右起第1位*右起第1位还是能到右起第3位的 
        }
        ans.len=len+b.len;//4位数*4位数不会超过8位数  
        while(ans.a[ans.len-1]==0&&ans.len>1)ans.len--;//查出实际长度 
        return ans;
    }
    BigInt operator /(const int &n)const//被确保被除数大 商为正 
    {
        BigInt ans;
        ans.len=len;
        int k=0;
        for(int i=ans.len-1;i>=0;i--)
        {
            k=k*mod+a[i];//k=上一位来的退位*10000+这一位 
            ans.a[i]=k/n;//这一位除以n 
            k=k%n;//这一位除以n的余数送给下一位,i=0最后一位如57/28余的1直接丢掉 取整  
        }
        while(ans.a[ans.len-1]==0&&ans.len>1)ans.len--;
        return ans;
    }
    ll operator %(const int &n)const
    {
        ll ans=0;
    	for(int i=len-1;i>=0;i--)
    	{
    		ans=ans*mod+a[i];
    		if(ans>n)ans%=n;
        }
        return ans;
    }
    void output()
    {
        printf("%d",a[len-1]);//是多少就是多少 没有前缀0 
        for(int i=len-2;i>=0;i--) 
            printf("%04d",a[i]);//包含前缀0 如0001 
        printf("\n");
    }
};
BigInt dp[2][111],tmp;
void init1()
{
	trie[0].tag=0;//向虚节点转移 
	//p.a[0][0]=26;
}
void init2(int N)
{
	memset(trie,0,sizeof trie);//只刷一个,之后插入的时候再刷 
	ans=tot=0;
	root=++tot;
	gets(s);
	for(int i=0;i<N;++i)id[s[i]]=i;
}
int main()
{
	init1(); 
    while(~scanf("%d%d%d",&n,&m,&p))
    {
    gets(s);
    init2(n);
    for(int i=0;i<p;i++)
    {
      gets(s);
      insert(root,s);
    }
    build(root,n);
    buildMatrix(n);
    //debug();puts(""); 
    int now=0;
    for(int i=1;i<tot;++i)dp[now][i].init(0);
	dp[now][0].init(1);//初始状态由0结尾 代表只能从0开始转移   
    //dp[i][j]表示 串长不超过i的 以j结尾的状态
	//类似后补1个的区间dp操作 初始化 
	//由串长不超过i-1的 以k结尾 状态k->状态j转移而来 
	for(int i=0;i<m;++i)//tot上限10*10 
    {
    	now^=1;
    	for(int j=0;j<tot;++j)//滚动数组的初始化  
		dp[now][j].init(0); //不然会受到i-2的答案的影响 重要 
    	for(int j=0;j<tot;++j)
    	{
    		for(int k=0;k<tot;++k)
    		{
    			if(!M.a[j][k])continue;
    			tmp.init(M.a[j][k]);
    			dp[now][k]=dp[now][k]+dp[now^1][j]*tmp; 
    		}
    	}
    }
    BigInt res;
	for(int i=0;i<tot;++i)//以任意结尾均可 因为初始化的时候 不可的均赋0 
	res=res+dp[now][i]; 
	res.output();
    }
    return 0;
}



版权声明:本文为Code92007原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。