1. 程式人生 > >●BZOJ 1444 [Jsoi2009]有趣的遊戲

●BZOJ 1444 [Jsoi2009]有趣的遊戲

sel eps strong mem log ati char 初始 消元

題鏈:

http://www.lydsy.com/JudgeOnline/problem.php?id=1444
題解.1:

概率dp,矩陣乘法,快速冪。
對所有串建立AC自動機,
那麽如果在trie樹的節點上轉移到一個打了標記的節點,就意味著該標記對應的人取得勝利。
(由於題中明確說明串長相同,串又互不相同,所以即表明著建立AC自動機後整個trie樹中只有n個打了標記的節點,同時不會存在某些節點無法轉移的問題。)
然後建立trie.size×trie.size大小的轉移矩陣trans,每個位置trans(i,j)表示i節點轉移到j節點的概率:
初始矩陣:
if(trie.tag[i]) trans(i,i)=1;


else trans(i,trie.ch[i][c])+=p[c](枚舉接下來的字符c)
此時這個矩陣的每個位置(i,j)就表明,從i走一步到j的概率。
然後將矩陣自乘很多次,就可以得到每個位置表示(i,j)從i走很多很多次後到j的概率。
那麽答案就是trans(1,i).(i為打了tag標記的節點):表示從初始位置走了很多很多次後到了一個串結尾位置的概率。
由於數據小,同時矩陣轉移了很多次,可以把矩陣裏存的十分接近理論概率值的概率直接看成答案。
復雜度((nl)^3logP)(轉移了P次矩陣,我定的是轉移23336666233336666ll多次)


代碼.1:

#include<bits/stdc++.h>
#define MAXN 15
using namespace std;
int N,M,L;
int pos[MAXN];
double p[MAXN];
struct Matrix{
	int r,c;
	double a[MAXN*MAXN][MAXN*MAXN];
	void Reset(int _r,int _c){
		r=_r; c=_c; memset(a,0,sizeof(a));
	}
	void Identity(){
		for(int i=1;i<=r;i++) a[i][i]=1;
	}
	Matrix operator * (const Matrix &rtm) const{
		Matrix now; now.Reset(r,rtm.c);
		for(int i=1;i<=now.r;i++)
			for(int j=1;j<=now.c;j++)
				for(int k=1;k<=c;k++)
					now.a[i][j]+=a[i][k]*rtm.a[k][j];
		return now;
	}
	Matrix operator ^ (long long b) const{
		Matrix now,base; base=*this;
		now.Reset(r,c); now.Identity();
		for(;b;base=base*base,b>>=1)
			if(b&1) now=now*base;
		return now;
	}
};
struct Trie{
	int size,p;
	int ch[MAXN*MAXN][MAXN],tag[MAXN*MAXN];
	Trie():size(1){}
	void Insert(char *S){
		static int cnt; p=1;
		for(int i=0;i<L;i++){
			int c=S[i]-‘A‘;
			if(!ch[p][c]) ch[p][c]=++size;
			p=ch[p][c];
		}
		tag[p]=1; pos[++cnt]=p;
	}
}T;
struct ACAM{
	int fail[MAXN*MAXN];
	void Build(){
		static queue<int>Q;
		Q.push(1); fail[1]=0;
		while(!Q.empty()){
			int u=Q.front(); Q.pop();
			T.tag[u]|=T.tag[fail[u]];
			for(int c=0;c<M;c++){
				int k=fail[u];
				if(!T.ch[u][c]){
					T.ch[u][c]=k?T.ch[k][c]:1;
					continue;
				}
				while(k&&!T.ch[k][c]) k=fail[k];
				fail[T.ch[u][c]]=k?T.ch[k][c]:1;
				Q.push(T.ch[u][c]);
			}
		}
	}
}A;
int main(){
	Matrix trans;
	static char S[MAXN];
	ios::sync_with_stdio(0);
	cin>>N>>L>>M;
	for(int i=0,a,b;i<M;i++)
		cin>>a>>b,p[i]=1.0*a/b;
	for(int i=1;i<=N;i++)
		cin>>S,T.Insert(S);
	A.Build();
	trans.Reset(T.size,T.size);
	for(int i=1;i<=T.size;i++){
		if(T.tag[i]) trans.a[i][i]=1;
		else for(int c=0;c<M;c++)
			trans.a[i][T.ch[i][c]]+=p[c];
	}
	trans=trans^23336666233336666ll;
	cout<<fixed<<setprecision(2);
	for(int i=1;i<=N;i++)
		cout<<trans.a[1][pos[i]]<<endl;
	return 0;
}

  

題解.2:

期望dp,高斯消元
對所有串建立AC自動機,那麽問題就轉變為類似 BZOJ_3143_[Hnoi2013]遊走 這種題目。
令dp[i]表示經過trie樹上的i號節點的期望次數,pro[j][i]表示從j點轉移到i點的概率。
那麽就可以列出如下轉移方程:
$$dp[i]=\sum_{j->i}{dp[j]*pro[j][i]}$$
特別的:
1.當j為trie樹是被打了個tag標記的節點時,則不能轉移給其他節點
2.當i為1號節點時,要多加一個數值1表示剛開始就期望經過了一次。
上述式子的轉移存在環,需要高斯消元。

因為到達了有tag標記的節點就結束遊戲不再轉移,所以期望到達所有tag節點的次數為1


也就是說,每個tag節點的期望就等於到達該節點對應的人勝利的概率。

復雜度O((nl)³)

代碼.2:

#include<bits/stdc++.h>
#define MAXN 15
using namespace std;
const double eps=1e-8;
int N,M,L,fail;
int id[MAXN];
double a[MAXN*MAXN][MAXN*MAXN],g[MAXN],dp[MAXN*MAXN];
double *A[MAXN*MAXN];
int dcmp(double x){
	if(fabs(x)<eps) return 0;
	return x>0?1:-1;
}
struct ACAM{
	int size;
	int ch[MAXN*MAXN][MAXN],tag[MAXN*MAXN],fail[MAXN*MAXN];
	ACAM():size(1){}
	void Insert(char *S){
		static int p,cnt; p=1; bool fg=0;
		for(int i=0;i<L;i++){
			int c=S[i]-‘A‘;
			if(!ch[p][c]) ch[p][c]=++size;
			p=ch[p][c];
		}
		tag[p]=1; id[++cnt]=p;
	}
	void Build(){
		static queue<int>Q;
		Q.push(1); fail[1]=0;
		while(!Q.empty()){
			int u=Q.front(); Q.pop();
			tag[u]|=tag[fail[u]];
			for(int c=0;c<M;c++){
				int k=fail[u];
				if(!ch[u][c]){
					ch[u][c]=k?ch[k][c]:1;
					continue;
				}
				while(k&&!ch[k][c]) k=fail[k];
				fail[ch[u][c]]=k?ch[k][c]:1;
				Q.push(ch[u][c]);
			}
		}
	}
}DS;
void buildequation(){
	for(int i=1;i<=DS.size;i++) if(!DS.tag[i])
		for(int c=0;c<M;c++)
			a[DS.ch[i][c]][i]+=g[c];
	for(int i=1;i<=DS.size;i++) a[i][i]+=-1;
	a[1][DS.size+1]+=-1;
	for(int i=1;i<=DS.size;i++) A[i]=a[i];
}
void Gausselimination(int pos,int i){
	if(pos==DS.size+1||i==DS.size+1) return;
	for(int j=pos;j<=DS.size;j++) if(dcmp(A[j][i])!=0){
		swap(A[j],A[pos]); break;
	}
	if(dcmp(A[pos][i])!=0)
		for(int j=pos+1;j<=DS.size;j++){
			double k=A[j][i]/A[pos][i];
			for(int l=i;l<=DS.size+1;l++)
				A[j][l]-=k*A[pos][l];
		}
	Gausselimination(pos+(dcmp(A[pos][i])!=0),i+1);
	if(dcmp(A[pos][i])!=0){
		for(int l=i+1;l<=DS.size;l++)
			dp[i]+=A[pos][l]*dp[l];
		dp[i]=A[pos][DS.size+1]-dp[i];
		dp[i]=dp[i]/A[pos][i];
	}
}
int main(){
	static char S[15];
	ios::sync_with_stdio(0);
	cin>>N>>L>>M;
	for(int i=0,P,Q;i<M;i++)
		cin>>P>>Q,g[i]=1.0*P/Q;
	for(int i=1;i<=N;i++)
		cin>>S,DS.Insert(S);
	DS.Build();
	buildequation();
	Gausselimination(1,1);
	cout<<fixed<<setprecision(2);
	for(int i=1;i<=N;i++)
		cout<<fabs(dp[id[i]])<<endl;
	return 0;
}

  

●BZOJ 1444 [Jsoi2009]有趣的遊戲