【BZOJ-4455】小星星 容斥 + 树形DP
4455: [Zjoi2016]小星星
Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 204 Solved: 137
[Submit][Status][Discuss]
Description
小Y是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有n颗小星星,用m条彩色的细线串了起来,每条细线连着两颗小星星。有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了n?1条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小Y找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。小Y想知道有多少种可能的对应方式。只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。
Input
第一行包含个2正整数n,m,表示原来的饰品中小星星的个数和细线的条数。
接下来m行,每行包含2个正整数u,v,表示原来的饰品中小星星u和v通过细线连了起来。
这里的小星星从1开始标号。保证u≠v,且每对小星星之间最多只有一条细线相连。
接下来n-1行,每行包含个2正整数u,v,表示现在的饰品中小星星u和v通过细线连了起来。
保证这些小星星通过细线可以串在一起。
n<=17,m<=n*(n-1)/2
Output
输出共1行,包含一个整数表示可能的对应方式的数量。
如果不存在可行的对应方式则输出0。
Sample Input
4 3
1 2
1 3
1 4
4 1
4 2
4 3
1 2
1 3
1 4
4 1
4 2
4 3
Sample Output
6
HINT
Source
Solution
一道容斥的好题
我们一共要满足两个限制:1.树中的一个点对应图中一个点,且一一对应 2.树中两点有边的,图中两点也对应有边
首先我们考虑最暴力的方法,如果同时满足两个限制,用$O(N^{N})$的时间去枚举,然后计数
那如果我们放宽一个限制,只统计满足限制2的数目,这显然可以用树形DP在$O(N^{3})$的时间里得到的
这里得到的是有$K$个点可以映射,但不保证$K$个点都被映射到,且不保证每个点只被映射一次
那么考虑用容斥去统计出答案,答案就是$Ans(N)-Ans(N-1)+Ans(N-2)-Ans(N-3)+Ans(N-4).....$
这样容斥的时候的枚举是$O(2^{N})$的
所以总的复杂度是$O(2^{N}×N^{3})$的
Code
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int read() { int 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*10+ch-'0'; ch=getchar();} return x*f; } #define LL long long #define MAXN 50 int N,M; struct EdgeNode{int next,to;}edge[MAXN<<1]; int head[MAXN],cnt=1; void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;} void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);} LL dp[MAXN][MAXN],ans,now,tmp; int lt[MAXN][MAXN],bin[MAXN],tot,a[MAXN]; void DFS(int now,int last) { for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) DFS(edge[i].to,now); for (int i=1; i<=tot; i++) { dp[now][i]=1; for (int j=head[now]; j; j=edge[j].next) if (edge[j].to!=last) { tmp=0; for (int k=1; k<=tot; k++) if (lt[a[k]][a[i]]) tmp+=dp[edge[j].to][k]; dp[now][i]*=tmp; } } } int main() { N=read(),M=read(); for (int x,y,i=1; i<=M; i++) x=read(),y=read(),lt[x][y]=lt[y][x]=1; for (int x,y,i=1; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y); bin[0]=1; for (int i=1; i<=N; i++) bin[i]=bin[i-1]<<1; for (int i=1; i<=bin[N]-1; i++) { now=0; tot=0; for (int j=1; j<=N; j++) if (i&bin[j-1]) a[++tot]=j; DFS(1,0); for (int j=1; j<=tot; j++) now+=dp[1][j]; if ((tot&1)==(N&1)) ans+=now; else ans-=now; } printf("%lld\n",ans); return 0; }
——It's a lonely path. Don't make it any lonelier than it has to be.