CF482C Game with Strings (状压期望 dp+高维前缀和)
状压期望 dp+高维前缀和
考虑固定一个要猜出的字符串,然后考虑期望 dp,状压目前已经猜了的字符位置,设 \(f_{s}\) 表示已经猜了的字符位置状态为 \(s\),最少还需要猜几次的期望值。那么转移枚举下一次要猜的位置 \(i\),有
\[f_{s}=1+\sum\frac{f_{s|2^i}}{tot}
\]
\(tot\) 表示剩余还没猜的位置。如果 \(s\) 的时候已经猜出,那么 \(f_s=0\)。可以预处理出状态 \(s\) 时,还不能分辨的字符串集合为 \(g_{s}\)。复杂度大概是 \(O(nm2^m)\) 的。
考虑说不要固定一个字符串,因为 \(E=\sum\frac{1}{n}F_{i}=\frac{1}{n}\sum F_{i}\),直接计算所有串的期望总和。那么转移就变成
\[f_{s}=g_s+\sum\frac{f_{s|2^i}}{tot}
\]
\(g_s\) 的求法,考虑一个猜字符的集合无法分辨出唯一一个字符串,那么一定存在另一个字符串的对应位置都与之相同。考虑枚举这么两个字符串,并求其极大相同字符集记录在 \(g_s\) 中。此时剩下的非极大相同字符集一定包含于已求的极大相同字符集中,高维前缀和求每个集合的超集即可。
复杂度 \(O(m2^m)\)。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 52, M = 22;
int n, m, lim;
char s[N][M];
double f[1 << M];
i64 g[1 << M];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n;
for(int i = 0; i < n; i++) {
std::cin >> s[i];
}
if(n == 1) {
std::cout << "0\n";
return 0;
}
m = strlen(s[0]), lim = (1 << m) - 1;
g[0] = (1LL << n) - 1;
for(int i = 0; i < n; i++) {
for(int j = i + 1; j < n; j++) {
int sta = 0;
for(int k = 0; k < m; k++) {
if(s[i][k] == s[j][k]) sta |= (1 << k);
}
g[sta] |= (1LL << i) | (1LL << j);
}
}
for(int i = 0; i < m; i++) {
for(int s = lim; ~s; s--) {
if(!(s & (1 << i))) g[s] |= g[s | (1 << i)];
}
}
for(int s = lim; ~s; s--) {
if(!g[s]) continue;
for(int i = 0; i < m; i++) {
if(!(s & (1 << i))) f[s] += f[s | (1 << i)];
}
f[s] /= (m - __builtin_popcountll(s));
f[s] += __builtin_popcountll(g[s]);
}
std::cout << std::fixed << std::setprecision(15) << f[0] / n << "\n";
return 0;
}