朴素容斥原理&[ZJOI2016][bzoj4455]小星星

11 篇文章 0 订阅
9 篇文章 0 订阅

前言

我容斥方面很菜啊,总是一头雾水,于是决心好好学容斥 (从水题刷起)

题意简介

题面链接

题目大意

给出一个 n n n个点, m m m条边的无向无重边、无自环的图,再给出一棵 n n n个点的树
定义一种对应为:让每个树中的点 u u u都对应一个图中的点 u ′ u' u(图中所有点都要被对应,也即对应的点必须不同)
问有多少对应方式使得对于树中每一组有边相连的点对 u , v u,v u,v,图中的对应点 u ′ , v ′ u',v' u,v之间也有边相连

数据范围

n ≤ 17 , m ≤ n ∗ ( n − 1 ) / 2 n\le17,m\le n*(n-1)/2 n17,mn(n1)/2

题解

部分分

直接dp
f [ i ] [ j ] [ S ] f[i][j][S] f[i][j][S]
表示 i i i节点及其子树都已经做完, i i i对应的图中节点是 j j j i i i节点及其子树中的点对应图中的节点集合是 S S S,此时的方案数
具体流程就是枚举每个状态
转移的时候是枚举子节点以及子节点对应的点,然后还要枚举子集( Θ ( 3 n ) \Theta(3^n) Θ(3n)
总复杂度是 Θ ( 3 n n 2 ) \Theta(3^nn^2) Θ(3nn2)

正解

考虑部分分的复杂度瓶颈在于枚举子集,考虑容斥优化
我们发现,由于有个限制“对应的点必须不同”,所以并不好做
考虑我们现放宽条件,求出能任意对应的时候的答案,再把哪些对应的点有相同的方案删去
打出容斥的式子:
∣ A 1 ∪ A 2 ∪ . . . ∪ A n ∣ = ∑ i = 1 n ( − 1 ) i − 1 ∑ ∣ T ∣ = i , T = { x 1 . . . x i } ∣ A x 1 ∩ A x 2 ∩ . . . ∩ A x i ∣ |{A_1}\cup{A_2}\cup...\cup{A_n}|=\sum_{i=1}^n(-1)^{i-1}\sum_{|T|=i,T=\{x_1...x_i\}}|{A_{x_1}}\cap{A_{x_2}}\cap...\cap{A_{x_i}}| A1A2...An=i=1n(1)i1T=i,T={x1...xi}Ax1Ax2...Axi
非常经典
A i A_i Ai表示未匹配 i i i的方案,设 U U U为所有的方案
那么相当于我们要求的就是 ∣ U ∣ − ∣ A 1 ∪ A 2 ∪ . . . ∪ A n ∣ |U|-|{A_1}\cup{A_2}\cup...\cup{A_n}| UA1A2...An
代入一下就是 A n s = ∣ U ∣ − ∑ i = 1 n ( − 1 ) i − 1 ∑ ∣ T ∣ = i , T = { x 1 . . . x i } ∣ A x 1 ∩ A x 2 ∩ . . . ∩ A x i ∣ = ∣ U ∣ + ∑ i = 1 n ( − 1 ) i ∑ ∣ T ∣ = i , T = { x 1 . . . x i } ∣ A x 1 ∩ A x 2 ∩ . . . ∩ A x i ∣ \begin{aligned} Ans&=|U|-\sum_{i=1}^n(-1)^{i-1}\sum_{|T|=i,T=\{x_1...x_i\}}|{A_{x_1}}\cap{A_{x_2}}\cap...\cap{A_{x_i}}|\\ &=|U|+\sum_{i=1}^n(-1)^i\sum_{|T|=i,T=\{x_1...x_i\}}|{A_{x_1}}\cap{A_{x_2}}\cap...\cap{A_{x_i}}| \end{aligned} Ans=Ui=1n(1)i1T=i,T={x1...xi}Ax1Ax2...Axi=U+i=1n(1)iT=i,T={x1...xi}Ax1Ax2...Axi
∣ A x 1 ∩ A x 2 ∩ . . . ∩ A x i ∣ |{A_{x_1}}\cap{A_{x_2}}\cap...\cap{A_{x_i}}| Ax1Ax2...Axi的意义是匹配的点不包含 x 1 ⋅ ⋅ ⋅ x i x_1···x_i x1xi,做的时候可以枚举 T T T的补集。 ∣ U ∣ |U| U的答案是空集补集的贡献。全集补集为空集、贡献为 0 0 0,所以不用枚举
做的时候枚举所有的点对应的点的集合 S S S,代表对应的点不能在 S S S之外,然后就 Θ ( n 3 ) \Theta(n^3) Θ(n3)的树形dp即可,总复杂度 Θ ( 2 n ∗ n 3 ) \Theta(2^n*n^3) Θ(2nn3)
复杂度分析数值比较大,但是容易发现后面的 Θ ( n 3 ) \Theta(n^3) Θ(n3)是不满的,复杂度分析在 1 e 8 1e8 1e8多一点,几乎不用卡常即可过

代码

贴上ac代码

#include<cstdio>
#include<cctype>
#include<algorithm>
#include<vector>
namespace fast_IO
{
    const int IN_LEN=10000000,OUT_LEN=10000000;
    char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
    inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
    inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
    inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
    char cu=getchar();x=0;bool fla=0;
    while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
    while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
const int maxn=19;
int n,m,bit[maxn];
bool edge[maxn][maxn]; 
std::vector<int>E[maxn];
int stack[maxn],top;
inline void get(int x)
{
	top=0;
	for(rg int i=1;x;i++,x>>=1)if(x&1)stack[++top]=i;
}
LL f[maxn][maxn],ans;
void dfs(const int u,const int fa)
{
	for(rg int t=1;t<=top;t++)f[u][t]=1;
	for(std::vector<int>::iterator Pos=E[u].begin();Pos!=E[u].end();Pos++)
	{
		const int v=*Pos;
		if(v==fa)continue;
		dfs(v,u);
		for(rg int t=1;t<=top;t++)
		{
			LL val=0;
			for(rg int l=1;l<=top;l++)
				if(edge[stack[t]][stack[l]])
					val+=f[v][l];
			f[u][t]*=val;
		}
	}
}
int main()
{
	bit[1]=1;
	for(rg int i=2;i<=18;i++)bit[i]=bit[i-1]<<1; 
	read(n),read(m);
	for(rg int i=1;i<=m;i++)
	{
		int u,v;read(u),read(v);
		edge[u][v]=edge[v][u]=1;
	}
	for(rg int i=1;i<n;i++)
	{
		int u,v;read(u),read(v);
		E[u].push_back(v),E[v].push_back(u);
	}
	for(rg int i=1;i<bit[n+1];i++)
	{
		get(i);
		dfs(1,0);
		LL val=0;
		for(rg int j=1;j<=top;j++)val+=f[1][j];
		if((top&1)==(n&1))ans+=val;
		else ans-=val;
	}
	print(ans);
	return flush(),0;
}

总结

这是容斥的经典模式,写篇博客来记录&纪念一下

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值