题目大意:
给出一个图,给出一棵树,你需要把树上的点映射到图上,两个点不能映射到同一个点。
要求若两个点在树上有一条边连着,那么映射到的点在原图上也要有一条边,求方案数。
考虑dp。
如果直接按照两个点不能映射到同一个点的限制来做,发现子状态比较难设计(好像
O
(
3
n
∗
n
)
O(3^n*n)
O(3n∗n)珂以做,但是会爆)
因此先考虑珂以有两个点映射到同一个点的情况qwq
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示以
i
i
i为根,且
i
i
i对应原图中的
j
j
j的方案数。
O
(
n
3
)
O(n^3)
O(n3)转移:
初始化:
d
p
[
x
]
[
i
]
=
1
dp[x][i]=1
dp[x][i]=1,因为若
x
x
x的子树中只有
x
x
x,那么
x
x
x珂以对应到原图任意一个点。
对于dfs遍历到的节点
x
x
x和
x
x
x的孩子
v
v
v,枚举
x
x
x映射到的点
j
j
j和
v
v
v映射到的点
k
k
k。
若
j
j
j和
k
k
k在原图中连了边,那么把
d
p
[
v
]
[
k
]
dp[v][k]
dp[v][k]加到
s
u
m
sum
sum中,最后根据乘法原理,让
d
p
[
x
]
[
j
]
dp[x][j]
dp[x][j]乘上
s
u
m
sum
sum。
然后发现这样子dp出来的结果有重复qwq,两个点珂以映射到同一个点
比如有3个点,1映射到2,2映射到2,3映射到1。
观察发现,这种情况就有一个点(3)没有被映射到qwq
所以减去这种有一个点没有映射到的情况就珂以了。
……
…………
………………
真的是介个样子吗?
考虑有两个点没被映射到的情况:
还是举3个点的例子,1映射到2,2映射到2,3也映射到2。
发现这样在1没有映射到的时候会减掉一次,3没有映射到的时候也会减掉一次,所以再加回来就珂以了。
因此用容斥,如果有奇数个点没有映射到,就减掉,偶数个点没有映射到就加上qwq
时间复杂度
O
(
2
n
n
3
)
O(2^nn^3)
O(2nn3)
毒瘤代码
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<vector>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
inline void write(const int x) {
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int Size=18;
const int MaxE=145;
int n,m,cnt,head[Size];
struct Edge {
int v,next;
} w[MaxE<<1];
void AddEdge(int u,int v) {
w[++cnt].v=v;
w[cnt].next=head[u];
head[u]=cnt;
}
bool G[Size][Size],del[Size];
ll dp[Size][Size]; //以i为根,且i对应原图中的j号点的方案数
void dfs(int x,int fa) {
for(re i=1; i<=n; i++) {
dp[x][i]=1;
}
for(int i=head[x]; i; i=w[i].next) {
int nxt=w[i].v;
if(nxt!=fa) {
dfs(nxt,x);
for(re j=1; j<=n; j++) {
if(del[j]) continue;
ll sum=0;
for(re k=1; k<=n; k++) {
if(del[k] || !G[j][k]) continue;
sum+=dp[nxt][k];
}
dp[x][j]*=sum;
}
}
}
}
inline void oper(re x) {
memset(del,0,sizeof(del));
re cnt=1;
while(x) {
if(x&1) del[cnt]=true;
x>>=1;
cnt++;
}
}
int main() {
n=read();
m=read();
for(re i=1; i<=m; i++) {
int u=read();
int v=read();
G[u][v]=G[v][u]=true;
}
for(re i=1; i<n; i++) {
int u=read();
int v=read();
AddEdge(u,v);
AddEdge(v,u);
}
ll ans=0;
for(re i=0; i<(1<<n); i++) {
oper(i);
dfs(1,0);
ll sum=0;
for(re j=1; j<=n; j++) {
sum+=dp[1][j];
}
if(__builtin_popcount(i)&1) {
ans-=sum;
} else {
ans+=sum;
}
}
printf("%lld",ans);
return 0;
}