CodeForces 482C Game with Strings (概率+狀壓DP 好題)* 第四百篇博文紀念
阿新 • • 發佈:2018-12-22
#include<bits/stdc++.h> using namespace std; #define debug puts("YES"); #define rep(x,y,z) for(int (x)=(y);(x)<(z);(x)++) #define ll unsigned long long #define lrt int l,int r,int rt #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 #define root l,r,rt #define mst(a,b) memset((a),(b),sizeof(a)) const int maxn =(1<<20)+5;///加括號 const int mod=9999991; const int ub=1e6; ll powmod(ll x,ll y){ll t; for(t=1;y;y>>=1,x=x*x%mod) if(y&1) t=t*x%mod; return t;} ll gcd(ll x,ll y){return y?gcd(y,x%y):x;} /* 題目大意:給定若干個串, 然後開始流程是這樣,一個人隨機從中選一個串, 問另一個人通過問位置上的字元的期望次數可以確定這個串(保證所有串都不一樣) 由於串的長度只有20,不難想到要用狀壓dp。 首先我們需要確定一個狀態s能標識出多少個不同串, 樸素演算法是對於每一個狀態,暴力列舉,但是複雜度不支援。 發現其中很多子狀態是包含關係,比如一個s不能確定的狀態是t, 那麼t肯定也存在於s 的子集中, 這樣先用狀壓搞定標識陣列,先通過列舉任意兩個串, 提取串中相同字元 的狀態,初始化標識陣列,然後通過從大到小列舉狀態來標記。 得到標識陣列後,設定dp陣列,即在走到s狀態後仍然無法確定所得串的概率。 可以用這個dp陣列去狀壓擴充,我們只要搞清擴充的邏輯是什麼就可以了。 假如我們得到了u狀態的概率,那麼對於下一個位置的選擇,其概率是1/(n-bit), bit是u狀態的1的個數,然後對於選中目標串的概率,我們用標識陣列去處理, 這樣就可以擴充dp陣列。 時間複雜度:O(2^20*20),記憶體不注意的話可能會爆掉,把bitcount陣列內聯。 */ char s[55][22]; int n,t,l; int mark[maxn]; ll tmp[maxn];///狀態s能區別出多少個 double dp[maxn];///dp狀態s表示選到s狀態後沒有得到目標串的概率 inline ll bitcount(ll x){return x?bitcount(x>>1)+(x&1):0;} void getmark() { rep(i,0,n) rep(j,i+1,n) { int S=0; rep(k,0,l) { S+=((s[i][k]==s[j][k])<<k); } tmp[S]|=((1LL<<i)|(1LL<<j)); } for(int i=t-1;i>=0;i--) { rep(j,0,l) if(i&(1<<j)) { tmp[i^(1<<j)]|=tmp[i]; } mark[i]=n-bitcount(tmp[i]); } } double p[55]; int main() { scanf("%d",&n); for(int i=0;i<n;i++) scanf("%s",s[i]); l=strlen(s[0]),t=(1<<l);///擴充成總狀態 getmark(); ///rep(i,0,t) cout<<mark[i]<<" ";puts(""); dp[0]=1.0; rep(i,0,t) { int bit=bitcount(i),tot=n-mark[i]; double mv=dp[i]/(l-bit); if(tot==0) continue; rep(j,0,l) if((i&(1<<j))==0) { int ts=i^(1<<j); double a=1.0*(mark[ts]-mark[i])/tot; dp[ts]+=mv*(1-a); p[bit+1]+=mv*a; } } double ans=0;rep(i,1,l+1) ans+=p[i]*i; printf("%.9f\n",ans);///這裡還不能用.lf return 0; }