CF482C Game with Strings
狀壓期望 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;
}