洛谷传送门
BZOJ传送门
题目描述
小Y是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有 n n n颗小星星,用 m m m条彩色的细线串了起来,每条细线连着两颗小星星。
有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了 n − 1 n-1 n−1条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小Y找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。小Y想知道有多少种可能的对应方式。
只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。
输入输出格式
输入格式:
第一行包含个 2 2 2正整数 n , m n,m n,m,表示原来的饰品中小星星的个数和细线的条数。接下来 m m m行,每行包含 2 2 2个正整数 u , v u,v u,v,表示原来的饰品中小星星 u u u和 v v v通过细线连了起来。这里的小星星从 1 1 1开始标号。保证 u ≠ v u≠v u̸=v,且每对小星星之间最多只有一条细线相连。接下来 n − 1 n-1 n−1行,每行包含个 2 2 2正整数 u , v u,v u,v,表示现在的饰品中小星星 u u u和 v v v通过细线连了起来。保证这些小星星通过细线可以串在一起。 n ≤ 17 , m ≤ n ∗ ( n − 1 ) / 2 n\le 17,m\le n*(n-1)/2 n≤17,m≤n∗(n−1)/2
输出格式:
输出共 1 1 1行,包含一个整数表示可能的对应方式的数量。如果不存在可行的对应方式则输出0。
输入输出样例
输入样例#1:
4 3
1 2
1 3
1 4
4 1
4 2
4 3
输出样例#1:
6
解题分析
看到 n n n这么小的数据范围, 首先想到的是状压 d p dp dp, 然后发现本来就给了我们一棵树, 直接记录子树的状态即可。
设 d p [ i ] [ s t a t ] dp[i][stat] dp[i][stat]表示 i i i子树取的点集合为 s t a t stat stat时的方案数, 然后我们发现似乎转移的时候需要知道子树哪个点作为接头, 否则无法知道是否可以转移。 因此我们还需要加一维状态: 设 d p [ i ] [ j ] [ s t a t ] dp[i][j][stat] dp[i][j][stat]表示 i i i子树取得点集合为 s t a t stat stat, i i i取的 j j j号点的方案数。
这样做看起来在合并子树的时候只需要枚举父节点已经合并出的状态, 然后枚举其补集中元素个数为 s i z [ s o n [ f a t ] ] siz[son[fat]] siz[son[fat]]的集合进行合并。 实则有个问题: 我们无法科学地枚举出补集中元素个数为指定个数的子集, 只能暴力枚举子集, 算出来总复杂度是 O ( n 3 3 n ) O(n^33^n) O(n33n)的, 卡不过。
既然我们无法通过记录状态转移, 那就换一种思路, 干脆不记录状态, 暴力转移, 不管重复的部分, 最后做个容斥就好了。 这样的复杂度是 O ( n 3 2 n ) O(n^32^n) O(n32n)的, 就可以过了。
代码如下:
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <cstring>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 20
#define ll long long
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
for (; !isdigit(c); c = gc);
for (; isdigit(c); c = gc)
x = (x << 1) + (x << 3) + c - 48;
}
bool out[MX], mp[MX][MX];
int head[MX], avai[MX];
ll dp[MX][MX], ans, res;
int n, m, cnt, tot;
struct Edge {int to, nex;} edge[MX << 1];
IN void add(R int from, R int to) {edge[++cnt] = {to, head[from]}, head[from] = cnt;}
void DP(R int now, R int fa)
{
ll sum;
for (R int i = 1; i <= tot; ++i) dp[now][avai[i]] = true;
for (R int i = head[now]; i; i = edge[i].nex)
{
if (edge[i].to == fa) continue;
DP(edge[i].to, now);
for (R int j = 1; j <= tot; ++j)
{
sum = 0;
for (R int k = 1; k <= tot; ++k)
sum += mp[avai[j]][avai[k]] * dp[edge[i].to][avai[k]];
dp[now][avai[j]] *= sum;
}
}
}
int main(void)
{
int foo, bar, all; in(n), in(m);
for (R int i = 1; i <= m; ++i)
{
in(foo), in(bar);
mp[foo][bar] = mp[bar][foo] = true;
}
for (R int i = 1; i < n; ++i)
in(foo), in(bar), add(foo, bar), add(bar, foo);
all = (1 << n) - 1;
for (R int i = 1; i <= all; ++i)
{
std::memset(dp, 0, sizeof(dp));
tot = res = 0;
for (R int j = 0; j < n; ++j)
if (i & (1 << j)) avai[++tot] = j + 1;
DP(1, 0);
for (R int j = 1; j <= tot; ++j) res += dp[1][avai[j]];
if ((n - tot) & 1) ans -= res;
else ans += res;
}
printf("%lld", ans);
}