1. 程式人生 > 實用技巧 >P4547 [THUWC2017]隨機二分圖(狀壓,期望DP)

P4547 [THUWC2017]隨機二分圖(狀壓,期望DP)

期望好題。

發現 \(n\) 非常小,應該要想到狀壓的。

我們可以先只考慮 0 操作。

最難的還是狀態:

我們用 \(S\) 表示左部點有哪些點已經有對應點, \(T\) 表示右部點有哪些點已經有對應點,\(f[S][T]\) 表示從一條邊沒連到此狀態的期望方案數

這樣就有轉移:

\[f[S][T] <- \sum_{s \in S,t \in T}f[S \oplus s][T \oplus t] * p(s, t) \]

也就是說,從沒選的點中選倆點連邊。不過這可能會算重(先連 \(e_1\) 後連 \(e_2\) 和先連 \(e_2\) 後連 \(e_1\) 都會計入答案)。因此我們強制要求選擇 \(S\)

中編號最小的點連邊,這樣每種答案就只被計算一次了。

然後考慮 1,2 操作。1,2 操作並沒有改變兩條邊各自出現的概率,只是改變的兩條邊同時出現的概率。因此我們可以加一些特殊的邊 \(e_1, e_2\) ,並要求 \(e_1, e_2\) 必須一起被選,這樣就可以調節同時出現的概率。具體來說就是 1 操作加 \(\frac{1}{4}\),2操作減 \(\frac{1}{4}\)

然後就沒啥難點了。考慮到合法狀態數不多,直接記憶化搜尋即可。

const int P = 1e9 + 7;
inline ll quickpow(ll x, int k) {
	ll res = 1;
	while (k) {
		if (k & 1)	res = res * x % P;
		x = x * x % P;
		k >>= 1;
	}
	return res;
}
int inv2, inv4;
int n, m;
struct edge {
	int s, t, p;
}e[N];
int ecnt;

map<int, ll> mp[NN];
inline ll dfs(int S, int T) {
	if (S == 0 && T == 0)	return 1;
	if (mp[S].count(T))	return mp[S][T];
	ll res = 0;
	for (register int i = 1; i <= ecnt; ++i) {
		int s = e[i].s, t = e[i].t, p = e[i].p;
		if (((S | s) != S) || ((T | t) != T))	continue;
		if ((s | (lowbit(S))) != s)	continue;
		res = (res + dfs(S ^ s, T ^ t) * p) % P;
	}
	mp[S][T] = res;
	return res;
}

int main() {
	inv2 = quickpow(2, P - 2);
	inv4 = quickpow(4, P - 2);
	read(n), read(m);
	for (register int i = 1; i <= m; ++i) {
		int t, x, y; read(t), read(x), read(y);
		--x, --y;
		e[++ecnt] = (edge){1 << x, 1 << y, inv2};
		if (t) {
			int a, b; read(a), read(b);
			--a, --b;
			e[++ecnt] = (edge){(1 << a), (1 << b), inv2};//Attention!
			if (a == x || b == y)	continue;
			e[++ecnt] = (edge){(1 << x) | (1 << a), (1 << y) | (1 << b), t == 1 ? inv4 : P - inv4};
		}
	}
	ll ans = dfs((1 << n) - 1, (1 << n) - 1) * (1 << n) % P;
	printf("%lld\n", ans);
	return 0;
}