P3706 [SDOI2017]硬币游戏

题目大意

\(n\) 个长度为 \(m\)0/1 串,在一个空串尾部不断随机添加 0/1

若出现 \(s_i\) 则第 \(i\) 个人获胜并停止游戏,求第 \(i\) 个人获胜的概率。

\(n,m \le 300\)

思路

这个游戏显然是可以结束的,即一定有赢家。

\(p_i\) 为第 \(i\) 个人获胜的概率,那么有 \(\sum_{i=1}^n p_i=1\)

\(N\) 为任意一个未终止状态出现的概率,考虑在 \(N\) 后面直接加入 \(s_i\) 使得 \(i\) 获胜,

\(N + s_i\) 可能并不合法,因为可能存在 \(s_j\) 先于 \(s_i\) 出现,可以枚举 \(j\) 和重复部分的长度 \(k\)

1.png

那么用总概率-不合法的概率有:

\[p_i=N\times \frac{1}{2^m}-\sum_{j=1}^n \sum_{k=1}^{m-1} [s_i[1,k]=s_j[m-k+1,k]]p_j \frac{1}{2^{m-k}} \]

意义是:下面局面出现的概率为 \(p_j\) , 再将后面的 \(m-k\) 位与上面对齐。

这样我们有了 \(n+1\) 个方程,直接高斯消元即可。

#include <cstdio>
#include <iostream>
using namespace std;
#define ull unsigned long long

template<typename _T>
_T Abs( _T x ) { return x < 0 ? -x : x; }

const int MAXN = 300;

double a[ MAXN + 5 ][ MAXN + 5 ];
void Gauss( int n ) {
//	for( int i = 1 ; i <= n ; i ++ )
//		for( int j = 1 ; j <= n + 1 ; j ++ )
//			printf("%.3f%c", a[ i ][ j ] , j == n + 1 ? '\n' : ' ' );
	for( int i = 1 ; i <= n ; i ++ ) {
		int pos = i;
		for( int j = i ; j <= n ; j ++ ) if( Abs( a[ pos ][ i ] ) < Abs( a[ j ][ i ] ) ) pos = j;
		if( pos != i ) swap( a[ pos ] , a[ i ] );
		for( int j = 1 ; j <= n ; j ++ ) if( i != j ) {
			double d = a[ j ][ i ] / a[ i ][ i ];
			for( int k = 1 ; k <= n + 1 ; k ++ ) a[ j ][ k ] -= a[ i ][ k ] * d;
		}
	}
	for( int i = 1 ; i <= n ; i ++ ) a[ i ][ n + 1 ] /= a[ i ][ i ];
}

int n , m; ull pw[ MAXN + 5 ] , hs[ MAXN + 5 ][ MAXN + 5 ];
char str[ MAXN + 5 ]; double pw2[ MAXN + 5 ];

ull Hash( int id , int l , int r ) {
	return hs[ id ][ r ] - hs[ id ][ l - 1 ] * pw[ r - l + 1 ];
} 
int main( ) {
	scanf("%d %d",&n,&m);
	
	pw[ 0 ] = 1; for( int i = 1 ; i <= m ; i ++ ) pw[ i ] = pw[ i - 1 ] * 131;
	for( int i = 1 ; i <= n ; i ++ ) {
		scanf("%s", str + 1 );
		for( int j = 1 ; j <= m ; j ++ ) hs[ i ][ j ] = hs[ i ][ j - 1 ] * 131 + ( str[ j ] == 'T' ? 1 : 2 );
	}
	
	pw2[ 0 ] = 1; for( int i = 1 ; i <= m ; i ++ ) pw2[ i ] = pw2[ i - 1 ] / 2;
	for( int i = 1 ; i <= n ; i ++ ) {
		a[ i ][ n + 1 ] = -pw2[ m ];
		for( int j = 1 ; j <= n ; j ++ ) {
			double p = 0;
			for( int k = 1 ; k < m ; k ++ ) if( Hash( i , 1 , k ) == Hash( j , m - k + 1 , m ) ) p += pw2[ m - k ];
			a[ i ][ j ] = p;
		} 
		a[ i ][ i ] ++;
	}
	for( int i = 1 ; i <= n ; i ++ ) a[ n + 1 ][ i ] = 1; a[ n + 1 ][ n + 2 ] = 1;
	Gauss( n + 1 );
	for( int i = 1 ; i <= n ; i ++ ) printf("%.6f\n", a[ i ][ n + 2 ] );
	return 0;
}
posted @ 2021-11-17 16:57  chihik  阅读(17)  评论(0编辑  收藏  举报