Vexoben
Feb 20, 2019
题目链接:uoj185
(刷新以获取数学公式)
题意
给定一张个点的图和个点的树,问两张图的点有多少种对应关系,使树是图的子图.
题解
显然可以想一个 的DP: 表示这颗子树,点对应图上点,子树中的点对应图上点集的方案数,然而能拿的分好像和暴力差不多.不过听说转移FMT优化一下可以做到,然而被卡常也不是很能过.
标算是一个的容斥.如果我们不要求树和图上的点一一对应,而是多个树上的点可以对应同一个图上的点,只要对应后的点之间有边即可,DP的复杂度可以降低为
枚举映射到的点集,那么答案就是
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 17;
int n, m, E, lim;
int a[N][N], bitcnt[1 << N];
int fir[N], nex[N << 1], arr[N << 1];
LL f[N][N];
inline void Add_Edge(int x, int y) {
nex[++E] = fir[x];
fir[x] = E; arr[E] = y;
}
void dfs(int x, int fa) {
for (int i = 0; i < n; ++i) {
if (lim >> i & 1) f[x][i] = 1;
}
for (int i = fir[x]; i; i = nex[i]) {
if (arr[i] == fa) continue;
dfs(arr[i], x);
for (int j = 0; j < n; ++j) {
if (~lim >> j & 1) continue;
LL tmp = 0;
for (int k = 0; k < n; ++k) {
if (a[j][k] && (lim >> k & 1)) {
tmp += f[arr[i]][k];
}
}
f[x][j] *= tmp;
}
}
}
LL calc() {
LL ans = 0;
memset(f, 0, sizeof f);
dfs(0, -1);
for (int i = 0; i < n; ++i) {
if (lim >> i & 1) {
ans += f[0][i];
}
}
return ans;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int x, y;
scanf("%d%d", &x, &y);
--x; --y;
a[x][y] = a[y][x] = 1;
}
for (int i = 1; i < n; ++i) {
int x, y;
scanf("%d%d", &x, &y);
--x; --y;
Add_Edge(x, y);
Add_Edge(y, x);
}
for (int i = 0; i < (1 << n); ++i) {
bitcnt[i] = (bitcnt[i >> 1]) + (i & 1);
}
LL ans = 0;
for (lim = 0; lim < (1 << n); ++lim) {
LL tmp = calc();
ans += (bitcnt[lim] & 1) ? tmp : -tmp;
}
printf("%lld\n", abs(ans));
return 0;
}