Codeforces 113 D. Museum

問題

n個の部屋がm本の通路により結ばれている。
二人が次のように移動する。

  • 最初それぞれa,bの部屋にいる。
  • 部屋ごとに定められている確率p[i]で、その場にとどまり、1-p[i]の確率で、通路を等確率で一つ選び、隣の部屋に移動する
  • 二人の移動先が同じ部屋だったらそこで移動が終了する。

二人がそれぞれの部屋で出会う確率を求めよ。

制約条件

n≦22
0.01≦p[i]≦0.99

方針

遷移行列を作って、収束するまで累乗する。
ただし定数倍が結構厳しいので、2つ工夫が必要。


一つは、二人の居る部屋(a,b)についてa≦bとして、行列の成分を半分にすること。
もう一つは、行列の乗算の途中で非正規化数(1e-100くらいの小さい数)が出てきたら、
計算がものすごく遅くなる(30倍くらいかかるらしい)ので、0に置き換えること。


これらの工夫により制限時間内に実行が終わるようになる。

ソースコード

int n,m,a,b;
vector<vector<int> > e;
double p[22],A[256][256],B[256][256];

void run()
{
	cin>>n>>m>>a>>b;
	a--; b--;
	if(a>b)swap(a,b);
	e.clear(); e.resize(n);
	rep(i,m){
		int s,t; cin>>s>>t;
		e[--s].pb(--t);
		e[t].pb(s);
	}
	rep(i,n)cin>>p[i];
	
	double prob[22][22]={};
	rep(i,n)rep(j,n){
		if(i==j)prob[i][i]=p[i];
		else if(find(all(e[i]),j)!=e[i].end())prob[i][j]=(1-p[i])/e[i].size();
	}
	for(int i=0,c=0;i<n;i++)for(int j=i;j<n;j++,c++){
		if(i==j)A[c][c]=1;
		else{
			for(int k=0,d=0;k<n;k++)for(int l=k;l<n;l++,d++){
				if(k==l)A[d][c]=prob[i][k]*prob[j][l];
				else A[d][c]=prob[i][k]*prob[j][l]+prob[i][l]*prob[j][k];
			}
		}
	}
	int N=n*(n+1)/2;
	
	rep(it,17){
		rep(i,N)rep(j,N)rep(k,N)B[i][j]+=A[i][k]*A[k][j];
		rep(j,N){
			double s=0;
			rep(i,N){
				A[i][j]=B[i][j];
				B[i][j]=0;
				if(A[i][j]<1e-20)A[i][j]=0;
			}
		}
	}
	for(int i=0,c=0;i<n;i++)for(int j=i;j<n;j++,c++)
	for(int k=0,d=0;k<n;k++)for(int l=k;l<n;l++,d++)
	if(i==j&&k==a&&l==b)printf("%.9f%c",A[c][d],' ');
	puts("");
}