考虑我们这种恰好的方案数。
可以考虑子集反演。
我们设每个点映射为(to_i),设全集映射为(S)。
我们发现其实最后我们要求是,有多少个映射满足树的条件且是一个排列。
那么我们发现其实排列不太好求。
我们不妨设(g(S))为每个点至多使用的都是(S)集合里的点。
设(f(S))为恰好使用了所有点一次以上。
那么有(g(S) = sumlimits_{T in S} f(T))
有(f(S) = sumlimits_{T in S} (-1)^{|S| - |T|}g(T))
然后因为(S_1 = {1,2,....n}),那么恰好使用它的方案一定是一个排列。
然后就很好做了
类似于树上背包,树边保证转移关系。
#include<iostream>
#include<cstdio>
#define ll long long
#define N 18
int n,m;
int S;
int A[N][N];
int v[N];
int Cnt[(1 << N)];
struct P{int to,next;}e[N << 1];
int head[N],cnt;
ll dp[N][N];
inline void add(int x,int y){
e[++cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
inline void dfs(int u,int fa,int s){
for(int i = 1;i <= v[0];++i)dp[u][i] = 1;
for(int i = head[u];i;i = e[i].next){
int vi = e[i].to;
if(vi == fa)continue;
dfs(vi,u,s);
for(int j = 1;j <= v[0];++j){
ll tmp = 0;
for(int k = 1;k <= v[0];++k)
if(A[v[j]][v[k]])tmp += dp[vi][k];
dp[u][j] *= tmp;
}
}
}
int main(){
scanf("%d%d",&n,&m);
S = (1 << n) - 1;
if(n == 1){puts("1");return 0;}
for(int i = 1;i <= m;++i){
int x,y;
scanf("%d%d",&x,&y);
A[x][y] = A[y][x] = 1;
}
for(int i = 1;i <= n - 1;++i){
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
ll ans = 0;
for(int s = 0;s <= S;++s){
Cnt[s] = Cnt[s >> 1] + (s & 1);
v[0] = 0;
for(int i = 1;i <= n;++i)if((s >> (i - 1)) & 1)v[++v[0]] = i;
dfs(1,0,s);
ll g = 0;
for(int i = 1;i <= v[0];++i)
g += dp[1][i];
ans += (n - Cnt[s] & 1) ? -g : g;
}
std::cout<<ans<<std::endl;
}