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;
    }
    


  • 相关阅读:
    Leetcode 121. Best Time to Buy and Sell Stock
    Leetcode 120. Triangle
    Leetcode 26. Remove Duplicates from Sorted Array
    Leetcode 767. Reorganize String
    Leetcode 6. ZigZag Conversion
    KMP HDU 1686 Oulipo
    多重背包 HDU 2844 Coins
    Line belt 三分嵌套
    三分板子 zoj 3203
    二分板子 poj 3122 pie
  • 原文地址:https://www.cnblogs.com/riskyer/p/3262963.html
Copyright © 2011-2022 走看看