CF482C Game with Strings

Fire_Raku發表於2024-06-07

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;
}