zoukankan      html  css  js  c++  java
  • hdu2243之AC自动机+矩阵乘法

    考研路茫茫——单词情结

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
    Total Submission(s): 2789    Accepted Submission(s): 782

    Problem Description
    背单词,始终是复习英语的重要环节。在荒废了3年大学生涯后,Lele也终于要开始背单词了。
    一天,Lele在某本单词书上看到了一个根据词根来背单词的方法。比如"ab",放在单词前一般表示"相反,变坏,离去"等。

    于是Lele想,如果背了N个词根,那这些词根到底会不会在单词里出现呢。更确切的描述是:长度不超过L,只由小写字母组成的,至少包含一个词根的单词,一共可能有多少个呢?这里就不考虑单词是否有实际意义。

    比如一共有2个词根 aa 和 ab ,则可能存在104个长度不超过3的单词,分别为
    (2个) aa,ab,
    (26个)aaa,aab,aac...aaz,
    (26个)aba,abb,abc...abz,
    (25个)baa,caa,daa...zaa,
    (25个)bab,cab,dab...zab。

    这个只是很小的情况。而对于其他复杂点的情况,Lele实在是数不出来了,现在就请你帮帮他。
     
    Input
    本题目包含多组数据,请处理到文件结束。
    每组数据占两行。
    第一行有两个正整数N和L。(0<N<6,0<L<2^31)
    第二行有N个词根,每个词根仅由小写字母组成,长度不超过5。两个词根中间用一个空格分隔开。
     
    Output
    对于每组数据,请在一行里输出一共可能的单词数目。
    由于结果可能非常巨大,你只需要输出单词总数模2^64的值。
     
    Sample Input
    2 3 aa ab 1 2 a
     
    Sample Output
    104 52
     
    Author
    linle
     
    Recommend
    lcy
    这道题从上午搞到现在终于是用两种方法搞完了
    在这里想说一句,输入的n,l其中l要用到64位,因为后面算26^1+26^2+...+26^l或者A^1+A^2+A^3+...+A^l时要用到(l+1)/2进行二分快速幂,而l+1可能会超int,网上很多都没说清楚
    第二个就是求26^1+26^2+...+26^l或者A^1+A^2+A^3+...+A^l都可以用二分进行快速幂或直接进行矩阵快速幂,在这里我两种方法都写了
    第三个就是计算26^1+26^2+...+26^l不要用等比公式变成(26^(l+1)-26)/25去进行快速幂计算,这样会出错,至于为什么出错自己调试调试就知道了
    第四个就是题目中说结果可能很大需要去mod 2^64,在这里直接定义变量unsigned __int64,这样超出的就自动截断了,相当于mod
    分析+题解请看代码
    第一种方法:用二分矩阵快速幂求A^1+A^2+...+A^l
    /*
    分析:相信做过poj2778的都知道如何求长度为n的模式串不包含病毒串的个数
    没做过的建议去做,此题是poj2778的加强版
    本题只需要求出长度<=n的所有串-包含病毒串的个数
    即26^1+26^2+26^3+...+26^n-(A^1+A^2+A^3+...+A^n);//A是状态矩阵,即在满足条件下到达另一个状态的个数
    26^1+...+26^n可以用快速幂求出h或者矩阵快速幂求出,A^1+...+A^n可以用矩阵二分快速幂求出或者构造:
    |1 26| |Sn  | |Sn+1    |
    |0 26|*|26^n|=|26^(n+1)|;//Sn=26^1+26^2+...+26^n
    
    |A 1| |Sn| |Sn+1|
    |0 1|*| A|=|A   |;//Sn=A+A^2+A^3+...+A^n
    
    只要:|A 1|
         |0 1|
    自乘n次与|S0|相乘即可,则可以用矩阵快速幂求 
    		 |A | 
    */
    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<queue>
    #include<algorithm>
    #include<map>
    #include<iomanip>
    #define INF 99999999
    using namespace std;
    
    const int MAX=30+10;
    //unsigned __int64 mod=1ll<<64;
    unsigned __int64 array[MAX][MAX],sum[MAX][MAX],temp[MAX][MAX],ans[MAX][MAX];
    __int64 l;
    int size,n; 
    char s[10];
    
    struct TrieNode{
    	bool mark;//标记是否是词根
    	int id;//记录节点序号
    	TrieNode *fail,*next[26]; 
    }*root,Node[MAX];
    
    TrieNode *New_TrieNode(){
    	memset(&Node[size],0,sizeof(TrieNode));
    	Node[size].id=size;
    	return &Node[size++];
    }
    
    void InsertNode(char *a){//插入词根 
    	TrieNode *p=root;
    	while(*a){
    		if(!p->next[(*a)-'a'])p->next[(*a)-'a']=New_TrieNode();
    		p=p->next[(*a)-'a'];
    		++a;
    	}
    	p->mark=true;
    }
    
    void Build_AC(){//建立AC自动机并构造初始矩阵array
    	memset(array,0,sizeof array); 
    	TrieNode *p,*next;
    	queue<TrieNode *>q;
    	q.push(root);
    	while(!q.empty()){
    		p=q.front();
    		q.pop();
    		for(int i=0;i<26;++i){
    			if(p->next[i]){
    				next=p->fail;
    				while(next && !next->next[i])next=next->fail;
    				p->next[i]->fail=next?next->next[i]:root;
    				if(p->next[i]->fail->mark)p->next[i]->mark=true;//表示这个前缀是词根,acg,ac
    				q.push(p->next[i]); 
    			}else p->next[i]=(p == root)?root:p->fail->next[i];//从p->id状态可以递推到p->fail->next[i]状态
    			if(!p->next[i]->mark)++array[p->id][p->next[i]->id];//表示到达的下一个状态非词根,则可以到达 
    		}
    	}
    }
    
    void MatrixInit(unsigned __int64 a[MAX][MAX],bool flag){//矩阵初始化 
    	for(int i=0;i<size;++i){
    		for(int j=0;j<size;++j){
    			if(flag)a[i][j]=array[i][j];//a=array
    			else a[i][j]=(i == j);//a=1
    		}
    	}
    }
    
    void MatrixAdd(unsigned __int64 a[MAX][MAX],unsigned __int64 b[MAX][MAX]){//矩阵相加,a=a+b 
    	for(int i=0;i<size;++i){
    		for(int j=0;j<size;++j)a[i][j]+=b[i][j];
    	}
    }
    
    void MatrixMult(unsigned __int64 a[MAX][MAX],unsigned __int64 b[MAX][MAX]){//矩阵相乘,a=a*b 
    	unsigned __int64 c[MAX][MAX]={0};
    	for(int i=0;i<size;++i){
    		for(int j=0;j<size;++j){
    			for(int k=0;k<size;++k){
    				c[i][j]+=a[i][k]*b[k][j];
    			}
    		}
    	}
    	for(int i=0;i<size;++i){
    		for(int j=0;j<size;++j)a[i][j]=c[i][j];
    	}
    }
    
    void MatrixPow(__int64 k){
    	MatrixInit(sum,0);//sum=1
    	MatrixInit(temp,1);//temp=array
    	while(k){
    		if(k&1)MatrixMult(sum,temp);
    		MatrixMult(temp,temp);
    		k>>=1;
    	}
    }
    
    void MatrixSum(__int64 k){//A^1+A^2+A^3+...+A^n
    	if(k == 1){MatrixInit(ans,1);return;}
    	MatrixSum(k/2);
    	MatrixPow((k+1)/2);//这里用到了k+1,而k+1可能会超int,所以k即l要用64位 
    	if(k&1){//A+(A+A^m)*(A^1+A^2+...);//m=(k+1)/2
    		MatrixInit(temp,1);//temp=A;
    		MatrixAdd(sum,temp);//sum=sum+temp=A^m+A
    		MatrixMult(ans,sum);//ans=ans*sum=(A^1+A^2+...)*(A^m+A)
    		MatrixAdd(ans,temp);//ans=ans+temp=ans+A=A^1+A^2+...)*(A^m+A)
    	}else{//(1+A^m)*(A^1+A^2+...);//m=(k+1)/2
    		MatrixInit(temp,0);//temp=1
    		MatrixAdd(temp,sum);//temp=temp+sum=1+A^m
    		MatrixMult(ans,temp);//ans=ans*temp=(A^1+A^2+...)*(1+A^m)
    	}
    }
    
    int main(){
    	while(scanf("%d%I64d",&n,&l)!=EOF){
    		size=2;
    		array[0][0]=1,array[0][1]=26;
    		array[1][0]=0,array[1][1]=26;
    		MatrixPow(l);//求26^1+26^2+...+26^l
    		unsigned __int64 all=sum[0][1];
    		printf("%I64u
    ",all);
    		size=0;
    		root=New_TrieNode();
    		for(int i=0;i<n;++i){
    			scanf("%s",s);
    			InsertNode(s);
    		}
    		Build_AC();
    		MatrixSum(l);//A^1+A^2+A^3+...+A^n
    		for(int i=0;i<size;++i)all-=ans[0][i];
    		printf("%I64u
    ",all);
    	}
    	return 0;
    }
    

    第二种方法:用包含矩阵的矩阵进行快速幂求A^1+A^2+A^3+...+A^l;//第一次这种方式写,不知道是不是我写错了,感觉效率增加不是很多,为什么别人说效率会增加4倍左右呢,有知道的大神请指教
    /*
    分析:相信做过poj2778的都知道如何求长度为n的模式串不包含病毒串的个数
    没做过的建议去做,此题是poj2778的加强版
    本题只需要求出长度<=n的所有串-包含病毒串的个数
    即26^1+26^2+26^3+...+26^n-(A^1+A^2+A^3+...+A^n);//A是状态矩阵,即在满足条件下到达另一个状态的个数
    26^1+...+26^n可以用快速幂求出h或者矩阵快速幂求出,A^1+...+A^n可以用矩阵二分快速幂求出或者构造:
    |1 26| |Sn  | |Sn+1    |
    |0 26|*|26^n|=|26^(n+1)|;//Sn=26^1+26^2+...+26^n
    
    |A 1| |Sn| |Sn+1|
    |0 1|*| A|=|A   |;//Sn=A+A^2+A^3+...+A^n
    
    只要:|A 1|
         |0 1|
    自乘n次与|S0|相乘即可,则可以用矩阵快速幂求 
    		 |A | 
    */
    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<queue>
    #include<algorithm>
    #include<map>
    #include<iomanip>
    #define INF 99999999
    using namespace std;
    
    const int MAX=30+10;
    //unsigned __int64 mod=1ll<<64;
    unsigned __int64 array[MAX][MAX],sum[2][2][MAX][MAX],temp[2][2][MAX][MAX];
    int size,n;
    __int64 l;
    char s[10];
    
    struct TrieNode{
    	bool mark;//标记是否是词根
    	int id;//记录节点序号
    	TrieNode *fail,*next[26]; 
    }*root,Node[MAX];
    
    TrieNode *New_TrieNode(){
    	memset(&Node[size],0,sizeof(TrieNode));
    	Node[size].id=size;
    	return &Node[size++];
    }
    
    void InsertNode(char *a){//插入词根 
    	TrieNode *p=root;
    	while(*a){
    		if(!p->next[(*a)-'a'])p->next[(*a)-'a']=New_TrieNode();
    		p=p->next[(*a)-'a'];
    		++a;
    	}
    	p->mark=true;
    }
    
    void Build_AC(){//建立AC自动机并构造初始矩阵array
    	memset(array,0,sizeof array); 
    	TrieNode *p,*next;
    	queue<TrieNode *>q;
    	q.push(root);
    	while(!q.empty()){
    		p=q.front();
    		q.pop();
    		for(int i=0;i<26;++i){
    			if(p->next[i]){
    				next=p->fail;
    				while(next && !next->next[i])next=next->fail;
    				p->next[i]->fail=next?next->next[i]:root;
    				if(p->next[i]->fail->mark)p->next[i]->mark=true;//表示这个前缀是词根,acg,ac
    				q.push(p->next[i]); 
    			}else p->next[i]=(p == root)?root:p->fail->next[i];//从p->id状态可以递推到p->fail->next[i]状态
    			if(!p->next[i]->mark)++array[p->id][p->next[i]->id];//表示到达的下一个状态非词根,则可以到达 
    		}
    	}
    }
    
    void MatrixInit(unsigned __int64 A[2][2][MAX][MAX],bool flag){//矩阵初始化 
    	for(int a=0;a<2;++a){
    		for(int b=0;b<2;++b){
    			for(int i=0;i<size;++i){
    				for(int j=0;j<size;++j){
    					if(flag){
    						if(a+b == 0)A[a][b][i][j]=array[i][j];//A[0][0]=array
    						else if(b == 1)A[a][b][i][j]=(i == j);//A[0][1]=A[1][1]=1
    						else A[a][b][i][j]=0;//A[1][0]=0
    					}else{
    						if(a == b)A[a][b][i][j]=(i == j);//A[0][0]=A[1][1]=1
    						else A[a][b][i][j]=0;//A[0][1]=A[1][0]=0;
    					}
    				}
    			}
    		}
    	}
    }
    
    void MatrixMult(unsigned __int64 A[2][2][MAX][MAX],unsigned __int64 B[2][2][MAX][MAX]){//矩阵相乘,a=a*b 
    	unsigned __int64 C[2][2][MAX][MAX]={0};
    	for(int a=0;a<2;++a){
    		for(int b=0;b<2;++b){
    			for(int c=0;c<2;++c){
    				for(int i=0;i<size;++i){
    					for(int j=0;j<size;++j){
    						for(int k=0;k<size;++k){
    							C[a][b][i][j]+=A[a][c][i][k]*B[c][b][k][j];
    						}
    					}
    				}
    			}
    		}
    	}
    	for(int a=0;a<2;++a){
    		for(int b=0;b<2;++b){
    			for(int i=0;i<size;++i){
    				for(int j=0;j<size;++j)A[a][b][i][j]=C[a][b][i][j];
    			}
    		}
    	}
    }
    
    void MatrixPow(__int64 k){
    	MatrixInit(sum,0);//sum=1
    	MatrixInit(temp,1);//temp=B=|A 1|
    	while(k){                 //|0 1|
    		if(k&1)MatrixMult(sum,temp);
    		MatrixMult(temp,temp);
    		k>>=1;
    	}
    }
    
    unsigned __int64 FastPow(unsigned __int64 a,int k){
    	unsigned __int64 ans=1;
    	while(k){
    		if(k&1)ans=ans*a;
    		a=a*a;
    		k>>=1;
    	}
    	return ans;
    }
    
    unsigned __int64 FastSum(__int64 k){
    	if(k == 1)return 26;
    	unsigned __int64 ans=FastSum(k/2);
    	unsigned __int64 a=FastPow(26ull,(k+1)/2);//这里用到了k+1,而k+1可能会超int,k要用64位 
    	if(k&1)return 26+(26+a)*ans;//26+(26+26^m)*(26^1+26^2+...),m=(k+1)/2
    	else return (1+a)*ans;//(1+26^m)*(26^1+26^2+...),m=(k+1)/2
    }
    
    int main(){
    	while(scanf("%d%I64d",&n,&l)!=EOF){
    		size=0;
    		root=New_TrieNode();
    		for(int i=0;i<n;++i){
    			scanf("%s",s);
    			InsertNode(s);
    		}
    		Build_AC();
    		MatrixPow(l);
    		unsigned __int64 ans=FastSum(l);
    		for(int j=0;j<size;++j){//只要求出最终的sum[0][1][0][i]的结果就行 
    			for(int k=0;k<size;++k){
    				ans-=sum[0][1][0][k]*array[k][j];
    			}
    		}
    		printf("%I64u
    ",ans);
    	}
    	return 0;
    }
    


  • 相关阅读:
    MFC中CTREECTRL的checkbox问题
    GLOG的使用说明
    安装Electron
    WIN32中DLL的建立
    MFC动态创建菜单
    C++迭代器
    VIM常用命令
    层次遍历二叉树
    sql存储过程中加引号
    Apache Tomcat 绿色版安装Service(服务)
  • 原文地址:https://www.cnblogs.com/riskyer/p/3262963.html
Copyright © 2011-2022 走看看