P3349 [ZJOI2016] 小星星 题解 - 容斥学习
前言 之前从来没学习过容斥模型,这次这个题作为我的第一个容斥例题学习。 正文 0x00 问题分析 这个问题可以抽象为:给定一个图 G 和一棵树 Tree 要求对于树重新编号,编号是一个 n 的排列,假…
前言
之前从来没学习过容斥模型,这次这个题作为我的第一个容斥例题学习。
正文
0x00 问题分析
这个问题可以抽象为:给定一个图 和一棵树 要求对于树重新编号,编号是一个 的排列,假如树上任意一个父亲编号为 任意其儿子的编号为 则需要满足在图 上有编号为 和 的连边。
显然有一个状压 dp ,即定义 为树上原本标号为 的节点被标号为 ,并且其子树使用了 这个集合中的编号。这样直接计算时间复杂度为 这样会直接 T 掉考虑优化。
可以发现时间复杂度卡在要考虑 这个限制。那么考虑去除 一维,直接跑多次 dp 每次限定一个可用于编号的集合 。但是这样就没有限制了,并没有办法保证排列了,但是可以保证边的限制合法。但是这样是典型的交容斥,如何理解呢?
考虑每一个解(不管是否是排列,只需要这个解边的限制合法)作为元素(这个元素是一个集合就是标号的一个子集,即 )设所有解的集合为 ,将这个解的 中的点作为其属性,我们要求的就是每一个含有这个属性(即编号)的集合 ,设包含 这个编号属性的元素集合为 。要求的就是所有 的交。
0x01 容斥推导
根据容斥公式有:
且对于任意集合 有:
即
实际上 就是可以用任意标号去标。
而对于
相当于在任意选了 个点不选的解的数量。发现这个统计是容易的即 时的解的数量。
记 表示 时的解的数量,最终得到了:
0x02 DP细化
现在可以放心的 DP 了,假设当前钦定可用于标号的点集为 ,有:
其中 表示在图 上是否有边 。
DFS DP 即可
0x03 代码实现
AC CODE
#include<bits/stdc++.h>
// #define ONLINE_JUDGE
#define INPUT_DATA_TYPE int
#define OUTPUT_DATA_TYPE long long
INPUT_DATA_TYPE read(){register INPUT_DATA_TYPE x=0;register char f=0,c=getchar();while(c<'0'||'9'<c)f=(c=='-'),c=getchar();while('0'<=c&&c<='9')x=(x<<3)+(x<<1)+(c&15),c=getchar();return f?-x:x;}void print(OUTPUT_DATA_TYPE x){register char s[20];register int i=0;if(x<0){x=-x;putchar('-');}if(x==0){putchar('0');return;}while(x){s[i++]=x%10;x/=10;}while(i){putchar(s[--i]+'0');}return;}
int n,m;
long long f[20][20];
std::vector<int> tree[20],scur;
std::bitset<20> graph[20];
void addEdge_tree(int u,int v){tree[u].push_back(v);tree[v].push_back(u);return;}
void addEdge_graph(int u,int v){graph[u][v]=graph[v][u]=1;return;}
void dfs(int u,int p){
for(auto v:tree[u])
if(v!=p)
dfs(v,u);
register long long sum;
for(auto x:scur){
f[u][x]=1;
for(auto v:tree[u]){
if(v==p) continue;
sum=0;
for(auto y:scur)
sum+=f[v][y]*graph[x][y];
f[u][x]*=sum;
}
}
return;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("name.in", "r", stdin);
freopen("name.out", "w", stdout);
#endif
register int i,u,v,s,maxs;
register long long ans=0;
n=read();
m=read();
for(i=0;i<m;++i){
u=read()-1;
v=read()-1;
addEdge_graph(u,v);
}
for(i=1;i<n;++i){
u=read()-1;
v=read()-1;
addEdge_tree(u,v);
}
maxs=(1<<n);
for(s=0;s<maxs;++s){
scur.clear();
for(i=0;i<n;++i)
if((s>>i)&1) scur.push_back(i);
dfs(0,0);
if((n-scur.size())&1)
for(auto id:scur)
ans-=f[0][id];
else
for(auto id:scur)
ans+=f[0][id];
}
print(ans);
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}
总结
容斥的题首先要想到一个次优的解法,并且想要优化的过程中需要发现其符合容斥的性质才能完成。
谢谢你,读到这里。
本文同步自洛谷专栏,保留原始正文与发布时间。在洛谷查看原文 ↗