BZOJ4455/UOJ185 [Zjoi2016]小星星

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!

题目链接:BZOJ4455

     UOJ185

正解:DP+容斥原理

解题报告:

  考虑暴力的话需要将一棵子树中的点去与另一个集合一一配对,而对于这种计数类问题,我们可以通过容斥来避开限制。

  也就是说不管非法情况,用容斥的方法直接统计,最后得到答案。

  我们先枚举整棵树对应的集合(一个映射),用f[i][j]表示以i为根的子树且i对应j的方案数,每次枚举根对应一个节点,再枚举每个儿子节点对应节点,乘起来就可以了。

  考虑我这样做,显然会有大量节点映射到一个节点上去了,所以用容斥原理同加异减一下,把每个状态的ans综合起来就好了。

  然而复杂度还是很虚,写了一发,然后被卡常了… 

  改变循环变量顺序,预处理可行的转移对象,终于AC了...

//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
#define RG register
const int MAXN = 19;
const int MAXM = 520;
int n,m,ecnt,first[MAXN],to[MAXM],next[MAXM],all;
int mp[MAXN][MAXN],cnt,a[MAXN];
int head[MAXN],match[MAXN];
LL ans,f[MAXN][MAXN];//f[i][j]表示以i为根的子树的方案数,其中i对应的是j
struct edge{ int to,next; }e[MAXM];
inline void link(int x,int y){ next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; }
inline void Link(int x,int y){ e[++ecnt].next=head[x]; head[x]=ecnt; e[ecnt].to=y; }
inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

inline void dp(RG int x,RG int fa){
	RG int i,s1,j,k; for(i=first[x];i;i=next[i]) { if(to[i]==fa) continue; dp(to[i],x); }
	RG LL now=0;
	for(s1=1;s1<=cnt;++s1) {//枚举当前子树的根选什么
		f[x][s1]=1;
		for(j=first[x];j;j=next[j]) {
			if(to[j]==fa) continue; now=0;
			for(k=head[a[s1]];k;k=e[k].next) now+=f[to[j]][ match[e[k].to] ];
			f[x][s1]*=now; 
			if(f[x][s1]==0) break;//剪枝...
		}
	}
}

inline void work(){
	n=getint(); m=getint(); all=(1<<n)-1; RG int i,j,x,y; RG LL now;
	for(i=1;i<=m;++i) { x=getint(); y=getint(); mp[x][y]=mp[y][x]=1; }
	for(i=1;i<n;++i) { x=getint(); y=getint(); link(x,y); link(y,x); }
	for(i=1;i<=all;++i) {
		x=i; cnt=0;
		for(j=1;j<=n;++j) {
			if(x&1) a[++cnt]=j; match[j]=cnt;
			x>>=1; if(x==0) break;
		}
		ecnt=0; memset(head,0,sizeof(head));
		for(j=1;j<=cnt;j++)
			for(y=1;y<=cnt;y++)
				if(mp[ a[j] ][ a[y] ]) 
					Link(a[j],a[y]);

		dp(1,0);
		now=0;
		for(j=1;j<=cnt;++j) now+=f[1][j];

		if((cnt&1) == (n&1)) ans+=now;
		else ans-=now;
	}
	printf("%lld",ans);
}

int main()
{
    work();
    return 0;
}

  

原文地址:https://www.cnblogs.com/ljh2000-jump/p/6480471.html