P3349 [ZJOI2016] 小星星 题解 - 容斥学习
前言
之前从来没学习过容斥模型,这次这个题作为我的第一个容斥例题学习。
正文
0x00 问题分析
这个问题可以抽象为:给定一个图
显然有一个状压 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;
}
总结
容斥的题首先要想到一个次优的解法,并且想要优化的过程中需要发现其符合容斥的性质才能完成。