BZOJ4455 小星星

闲扯

看到多个限制条件的计数题目,就想到容斥原理

思路

题目要求两个条件
- 编号一一对应
- 树上存在的边,在图上映射到的点上也应该存在
考虑一个暴力的dp,设(dp_{i,j})表示i点编号对应到j点的方案数量
转移显然是枚举每个子节点和每个子节点对应的编号
对于每个子节点的不同方案数求和,不同子节点之间乘起来即可,复杂度(O(n^3))
然后这样是错的,因为编号会有重复,不符合限制,考虑容斥
(2^n)枚举子集表示哪几个编号不可以被对应,因为每有一个编号不可对应,就代表至少多出一对重复编号的点,就相当于至少重复0次-至少重复1次+至少重复2次...,最后就是一次都不重复的个数了,容斥一下就行了

复杂度(O(2^n n^3))

略微卡常,需要吸氧

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int u[100],v[100],fir[100],nxt[100],cnt,n,m;
void addedge(int ui,int vi){
    ++cnt;
    u[cnt]=ui;
    v[cnt]=vi;
    nxt[cnt]=fir[ui];
    fir[ui]=cnt;
}
int mat[100][100],stack[100],topx;
long long ans,dp[100][100];
void dfs(int u,int fa){
    for(int i=fir[u];i;i=nxt[i]){
        if(v[i]==fa)
            continue;
        dfs(v[i],u);
    }
    for(int i=1;i<=topx;i++){
        dp[u][i]=1;
        for(int j=fir[u];j;j=nxt[j]){
            if(v[j]==fa)
                continue;
            long long tmp=0;
            for(int k=1;k<=topx;k++)
                if(mat[stack[i]][stack[k]])
                    tmp+=dp[v[j]][k];
            dp[u][i]*=tmp;
        }
    }
}
signed main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d %d",&a,&b);
        mat[a][b]=mat[b][a]=1;        
    }
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d %d",&a,&b);
        addedge(a,b);
        addedge(b,a);
    }
    for(int S=0;S<(1<<n);S++){
        // printf("S=%lld ok
",S);
        memset(dp,0,sizeof(dp));
        topx=0;
        for(int i=1;i<=n;i++)
            if((1<<(i-1))&S)
                stack[++topx]=i;
        dfs(1,0);
        long long tmp=0;
        for(int i=1;i<=topx;i++){
            tmp+=dp[1][i];
        }
        ans+=tmp*(((n-topx)%2)?-1:1);
    }
    printf("%lld
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/dreagonm/p/10427527.html