朝暮(枚举基准 容斥)
给出一个n个点,最多n+6条边的无向图,求将树黑白染色,且没有相邻黑点的方案数。(1leqslant nleqslant 10^5)
首先,如果这个图是个树,就是个斯波dp。
然后我在考试时想了个做法:枚举所有非树边旁边是00还是10还是01。复杂度(n3^n),成功tle。
正解就好写得多。枚举旁边都是黑点的非树边有几条,容斥一下即可。
我其实还没搞懂为什么还能这样容斥……果然自己计数题太弱了。
注意((-1)^x)到底是啥!被坑了好久。
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=1e5+10, mod=1e9+7;
int n, m;
struct Edge{
int to, nxt;
}e[maxn*2];
int fir[maxn], cnte;
void addedge(int x, int y){
Edge &ed=e[++cnte];
ed.to=y; ed.nxt=fir[x]; fir[x]=cnte;
}
inline int popcount(int x){ int re=0;
while (x) re+=x&1, x>>=1;
return re; }
int qd[maxn], ex[6], ey[maxn], cntext;
long long f[maxn][2], ans;
int fa[maxn]; //有向图求返祖边才需要dfs!无向图只需要并查集
int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); }
void dfs(int now, int par){
f[now][0]=f[now][1]=1;
if (qd[now]) f[now][0]=0;
for (int i=fir[now]; i; i=e[i].nxt){
if (e[i].to==par) continue;
dfs(e[i].to, now);
(f[now][0]*=(f[e[i].to][0]+f[e[i].to][1]))%=mod;
(f[now][1]*=f[e[i].to][0])%=mod;
}
}
int main(){
//freopen("20.in", "r", stdin); freopen("20.out", "w", stdout);
scanf("%d%d", &n, &m); int x, y;
for (int i=1; i<=n; ++i) fa[i]=i;
for (int i=1; i<=m; ++i){
scanf("%d%d", &x, &y);
if (find(x)==find(y)){
ex[cntext]=x; ey[cntext++]=y;
continue;
}
addedge(x, y); addedge(y, x);
fa[find(x)]=find(y);
}
for (int i=0; i<(1<<cntext); ++i){
memset(f, 0, sizeof(f));
memset(qd, 0, sizeof(qd));
for (int j=0; j<cntext; ++j)
if (i&(1<<j)) qd[ex[j]]=qd[ey[j]]=1;
dfs(1, 0);
//要判断的是popcount的奇偶性!
(ans+=(popcount(i)&1?-1:1)*(f[1][0]+f[1][1]))%=mod;
}
printf("%lld
", (ans+mod)%mod);
return 0;
}