Solution
观察发现 (mleq 20) ,那么我们可以想到状压DP。
将位置压起来,设 (f_i) 表示此时状态为 (i) ,距离确定一个字符串所需要的期望,那么可得:
[f_i=sum_{!(iAnd(1<<j)}frac {f_{i|(1<<j)}}{tot}+1
]
其中 (tot) 表示状态 (i) 中还有几个位置没问(也就是还有几个 (0) ),那因为询问剩余位置的概率相同,所以此方程是对的。
但是每次询问不一定就能唯一一个字符串,可能是好几个,所以需要处理出来对于每一个状态能确定几个字符串。
那么设 (num_i) 表示符合 (i) 这个状态的字符串的数量,可得:
[f_i=sum_{!(iAnd(1<<j)}frac {f_{i|(1<<j)}}{tot} imes frac {num_{i|(1<<j)}}{num_i}+1
]
详细解释一下 (dfrac {num_{i|(1<<j)}}{num_i}) :首先设 (k=i|(1<<j)) ,那么 (num_kleq num_i) ,因为 (k) 多了一位确定的。而 (i) 有 (num_k) 种可能变成 (k) ,剩下的 (num_i-num_k) 是直接得到唯一确定字符串的,所以继续转移的可能就是 (dfrac {num_{i|(1<<j)}}{num_i}) 。
好,转移方程得到了,怎么求得 (num) 数组呢?
这个时候,就要再来一个辅助的东西——设 (unf_i) 表示状态为 (i) 的情况下,哪些字符串是符合的,或者说是不能直接确定的(这里是拿二进制状态存串是否满足条件),因为在 (i) 这个状态下,某几个串表达的东西可能一样。
对于 (unf) 数组,直接 (O(n^2)) 暴力匹配即可,然后从大到小更新。
那么 (num_i) 就是 (unf_i) 中 (1) 的数量了。
注意:代码中状压时枚举要往后错一位。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=(1ll<<20)+10,M=55;
int m,n,unf[N],num[N];
char s[M][M];
double f[N];
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
m=strlen(s[1]+1);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++){
int tmp=0;
for(int k=1;k<=m;k++)
if(s[i][k]==s[j][k]) tmp|=(1ll<<(k-1));
unf[tmp]|=(1ll<<(i-1)); unf[tmp]|=(1ll<<(j-1));
}
for(int i=(1ll<<m)-1;i>=1;i--)
for(int j=1;j<=m;j++)
if(i&(1ll<<(j-1))) unf[i^(1ll<<(j-1))]|=unf[i];
for(int i=0;i<(1ll<<m);i++)
for(int j=1;j<=n;j++)
if(unf[i]&(1ll<<(j-1))) num[i]++;
for(int i=(1ll<<m)-2;i>=0;i--){
if(!num[i]) continue;
int tot=m;
for(int j=1;j<=m;j++)
if(i&(1ll<<(j-1))) --tot;
for(int j=1;j<=m;j++){
if(i&(1ll<<(j-1))) continue;
f[i]+=
f[i|(1ll<<(j-1))]/(double)tot*((double)num[i|(1ll<<(j-1))]/(double)num[i]);
}
f[i]+=1.0;
}
printf("%.10lf
",f[0]);
return 0;
}