CF1773G

· · 题解

Problem

状压 dp 绝世好题。

看到 m 非常小,我们考虑压一手,设 f_S 为剩余选手为 S 时 Genie 获胜得概率。

但是现在有个问题是,我们肯定要选择一个问题让 S 转移到 T,但是我们不知道这个问题之前有没有被选过,如果重复选择不就寄了,如果再开一维记录问题得状态,2^{2\cdot 10^5} 也寄了,怎么办?

这里用到了 UOJ370 的 trick:如果某个问题被重复选择,则集合 S 在问完这个问题后不会发生变化,因为不会这个问题的人都寄了。

接下来考虑如何转移。我们设 g_{S,T} 表示为可以令参赛者状态从 S 转移到 T 问题个数,显然 T\subset S,所以 g 的空间是 \Theta(3^n) 的,可以存的下。然后显然易得的是转移方程 f_S=\frac{\sum\limits_{T\subset S} f_T\cdot g_{S,t}}{n-g_{S,\emptyset}-g_{S,S}},那么问题在于如何求 g

然后就做完了,复杂度 $\Theta(nm+3^m)$。 ### Code ```cpp #include<bits/stdc++.h> //#define int long long #define ll long long #define ull unsigned long long #define ld long double #define PII pair<int,int> #define INF 0x3f3f3f3f #define INFLL 0x3f3f3f3f3f3f3f3f #define chkmax(a,b) a=max(a,b) #define chkmin(a,b) a=min(a,b) #define rep(k,l,r) for(int k=l;k<=r;++k) #define per(k,r,l) for(int k=r;k>=l;--k) #define cl(f,x) memset(f,x,sizeof(f)) using namespace std; const int N=2e5+5,N1=17,M=1<<17; vector<PII> g[M]; int tmp[N]; ld f[N]; char s[N1]; signed main() { int n,m; scanf("%d%d",&n,&m); int tot=(1<<m)-1; rep(S,0,tot) { int len=1<<__builtin_popcount(S); g[S].resize(len); for(int T=S;;T=(T-1)&S) { g[S][--len].first=T; if(T==0) break; } } rep(i,1,n) { scanf("%s",s); int res=0; rep(j,0,m-1) { if(s[j]=='1') res|=1<<j; } ++g[tot][res].second; } per(S,tot-1,0) { int k=-1; rep(i,0,m-1) { if(!((S>>i)&1)) { k=i; break; } } int t=S|(1<<k); for(auto x:g[S]) tmp[x.first]=0; for(auto x:g[t]) tmp[S&x.first]+=x.second; for(auto &x:g[S]) x.second=tmp[x.first]; } rep(S,0,tot) { if((S+1)&1) continue; int cnt=n-g[S][0].second-g[S][(int)g[S].size()-1].second; if(cnt==0) f[S]=1.0; else { for(auto x:g[S]) { int T=x.first; if(T!=0&&T!=S) f[S]+=f[T]*x.second; } f[S]/=cnt; } } printf("%.12Lf\n",f[tot]); return 0; } ```