解题思路
数据规模 n ≤ 10 n\le10 n≤10 的 dp 题,显然为状压 dp。
状态定义: d p [ i ] [ j ] dp[i][j] dp[i][j] 为联通情况为 i i i,叶子结点情况为 j j j 时的方案数。
状态转移方程:和正常的状压差不多,这里不好描述,详见代码。
边界处理: d p [ 1 < < ( i − 1 ) ] [ 1 < < ( i − 1 ) ] = 1 ( i ∈ [ 1 , n ] ) dp[1<<(i-1)][1<<(i-1)]=1(i \in [1,n]) dp[1<<(i−1)][1<<(i−1)]=1(i∈[1,n])。
这道紫题真得有那么简单嘛?WA。
如果直接这样 DP,顺序不同的方案也会被重复计算。如以下情况:
我们发现是因为不同的根节点导致的,所以我们加入一个判断条件:
j j j 加入时当且仅当最高位是最新加入的。
22.2.9 Upd
洛谷上没审核通过,兔队让我用集合的语言写/kk
重新写了一下:
定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示目前生成树状态为 i i i 时,其中度数为 1 1 1 的点的状态为 j j j 时的方案数。
对于加边,可以从连到度数为 1 1 1 的点和其它点的情况中取最大值,状态转移方程见代码。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 1 << 10;
int s[N], head[15], cnt;
long long dp[N][N];
struct edge {
int v, nxt;
} e[205];
inline void add(int u, int v) {
e[++cnt] = {v, head[u]};
head[u] = cnt;
}
int main() {
ios :: sync_with_stdio(0);
int n, m, k;
cin >> n >> m >> k;
while (m--) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
int S = (1 << n) - 1;
for (int i = 1; i <= S; ++i) {
for (int j = 1; j <= n; ++j) {
if (i & (1 << j - 1)) {
++s[i];
}
}
}
for (int i = 1; i <= n; ++i) {
dp[1 << i - 1][1 << i - 1] = 1;
}
for (int i = 1; i <= S; ++i) {
for (int j = i; j; --j &= i) {
if (!dp[i][j]) {
continue;
}
for (int p = 1; p <= n; ++p) {
if (i & (1 << p - 1)) {
for (int l = head[p]; l; l = e[l].nxt) {
int v = e[l].v, t;
if (i & (1 << v - 1)) {
continue;
}
t = s[i] == 1 ? (i | (1 << v - 1)) : (j & ~(1 << p - 1) | (1 << v - 1));
if (!(t >> v)) {
dp[i | (1 << v - 1)][t] += dp[i][j];
}
}
}
}
}
}
long long res = 0;
for (int i = 1; i <= S; ++i) {
res += (s[i] == k) * dp[S][i];
}
cout << res << '\n';
return 0;
}
Good Good 贺题,Day Day Up!!