前言
我容斥方面很菜啊,总是一头雾水,于是决心好好学容斥 (从水题刷起)
题意简介
题目大意
给出一个
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 n≤17,m≤n∗(n−1)/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}}|
∣A1∪A2∪...∪An∣=i=1∑n(−1)i−1∣T∣=i,T={x1...xi}∑∣Ax1∩Ax2∩...∩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}|
∣U∣−∣A1∪A2∪...∪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=∣U∣−i=1∑n(−1)i−1∣T∣=i,T={x1...xi}∑∣Ax1∩Ax2∩...∩Axi∣=∣U∣+i=1∑n(−1)i∣T∣=i,T={x1...xi}∑∣Ax1∩Ax2∩...∩Axi∣
∣
A
x
1
∩
A
x
2
∩
.
.
.
∩
A
x
i
∣
|{A_{x_1}}\cap{A_{x_2}}\cap...\cap{A_{x_i}}|
∣Ax1∩Ax2∩...∩Axi∣的意义是匹配的点不包含
x
1
⋅
⋅
⋅
x
i
x_1···x_i
x1⋅⋅⋅xi,做的时候可以枚举
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)
Θ(2n∗n3)
复杂度分析数值比较大,但是容易发现后面的
Θ
(
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;
}
总结
这是容斥的经典模式,写篇博客来记录&纪念一下