朝暮(枚举基准 容斥)

朝暮(枚举基准 容斥)

给出一个n个点,最多n+6条边的无向图,求将树黑白染色,且没有相邻黑点的方案数。\(1\leqslant n\leqslant 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\n", (ans+mod)%mod);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/MyNameIsPc/p/9459893.html