「SDOI2017」硬幣遊戲
阿新 • • 發佈:2019-03-23
後綴 bstr while 等於 scanf 問題 names 發現 github
位就到了\(j\)。這種情況下,串\(i\)長度為\(k\)的前綴就等於串\(j\)長度為\(k\)的後綴。此時就相當於在\(P_j\)後接一個長為\(m-k\)的串到\(P_i\),而這樣的概率是\(\frac{1}{2^{m-k}}P_j\)。
題目鏈接
問題分析
首先一個顯然的做法就是建出AC自動機,然後高斯消元。但是這樣的復雜度是\(O(n^3m^3)\)的。
我們發現其實只需要求AC自動機上\(n\)個狀態的概率,而其余的概率是沒有用的。我們不妨設\(i\)贏的概率是\(P_i\)。同時,我們令\(P_0\)為沒有任何一個人贏的概率。
然後我們考慮從\(P_0\)轉移到\(P_i\)。如果我們直接在\(P_0\)後面加上串\(i\)是可以的。這樣的概率是\(\frac{1}{2^m}P_0\)。
但是這樣有一個問題:
我們從\(P_0\)轉移到\(P_i\)的過程中,可能先轉移到了\(P_j\)。比如說,我們在\(P_0\)後加了\(k(0 < k < m)\)
可以借助下圖加深理解:
所以我們可以得到\(n\)個方程
\[
P_i=\frac{1}{2^m}P_0-\sum_{j=1}^n\sum_{k=1}^m[substr(i,1,k)=substr(j,m-k+1)]\frac{1}{2^{m-k}}P_j
\]
其中\(substr(i,j,k)\)表示串\(i\)從\(j\)到\(k\)所構成的子串。
然後還有\(\sum\limits_{i=1}^nP_i=1\),這樣我們就有\(n+1\)個未知數,\(n+1\)個方程。然後你就穩了。
參考程序
#include <bits/stdc++.h> using namespace std; const int Maxn = 310; int n, m, A[ Maxn ][ Maxn ], Fail[ Maxn ][ Maxn ]; long double Pow[ Maxn ], B[ Maxn ][ Maxn ]; int main() { scanf( "%d%d", &n, &m ); for( int i = 1; i <= n; ++i ) { char Ch[ Maxn ]; scanf( "%s", Ch + 1 ); for( int j = 1; j <= m; ++j ) A[ i ][ j ] = ( Ch[ j ] == 'T' ) ? 1 : 0; } for( int i = 1; i <= n; ++i ) { Fail[ i ][ 1 ] = 0; int t = 0; for( int j = 1; j < m; ++j ) { while( t && A[ i ][ t + 1 ] != A[ i ][ j + 1 ] ) t = Fail[ i ][ t ]; if( A[ i ][ t + 1 ] == A[ i ][ j + 1 ] ) ++t; Fail[ i ][ j + 1 ] = t; } } Pow[ 0 ] = 1; for( int i = 1; i <= m; ++i ) Pow[ i ] = Pow[ i - 1 ] * 0.5L; for( int i = 1; i <= n; ++i ) for( int j = 1; j <= n; ++j ) { B[ i ][ j ] = 0ll; int t = 0; for( int k = 1; k <= m; ++k ) { while( t && A[ i ][ t + 1 ] != A[ j ][ k ] ) t = Fail[ i ][ t ]; if( A[ i ][ t + 1 ] == A[ j ][ k ] ) ++t; } if( i == j ) t = Fail[ i ][ t ];//註意不要漏掉這句 while( t ) { B[ i ][ j ] += Pow[ m - t ]; t = Fail[ i ][ t ]; } } for( int i = 1; i <= n; ++i ) { B[ i ][ 0 ] = -Pow[ m ]; B[ i ][ i ] += 1ll; } for( int i = 1; i <= n; ++i ) B[ 0 ][ i ] = 1; B[ 0 ][ n + 1 ] = 1; for( int i = 0; i <= n; ++i ) { if( B[ i ][ i ] == 0ll ) for( int j = i + 1; j <= n; ++j ) { if( B[ j ][ i ] ) for( int k = 0; k <= n + 1; ++k ) swap( B[ i ][ k ], B[ j ][ k ] ); break; } long double t = B[ i ][ i ]; for( int j = 0; j <= n + 1; ++j ) B[ i ][ j ] /= t; for( int j = 0; j <= n; ++j ) { if( j == i ) continue; long double T = B[ j ][ i ]; for( int k = 0; k <= n + 1; ++k ) B[ j ][ k ] -= B[ i ][ k ] * T; } } for( int i = 1; i <= n; ++i ) printf( "%.10Lf\n", B[ i ][ n + 1 ] ); return 0; }
「SDOI2017」硬幣遊戲