[ZJOI2016]小星星(容斥+dp)

洛谷连接:https://www.luogu.org/problemnew/show/P3349
题意至关于给一棵树从新赋予彼此不一样的编号,要求树上相邻的两个节点在给定的另一个无向图中也存在边相连。
n很小,但枚举阶乘确定是会爆炸的。
发现编号彼此不一样对统计答案的影响太大了,咱们能够尝试先让编号能够重复,可是限制能够选用的编号集,即O(2^n)枚举n个数的子集,而后容斥一下答案。
可选用的编号集合肯定了,编号还能够重复,接下来直接跑树形dp就能够了。f(u)(j)存的是u节点映射向j,子树内的总方案数。c++

#include<bits/stdc++.h>
using namespace std;
const int N=40;
typedef long long ll;
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
#define il inline
int gr,h[N],nxt[N],to[N];
il void tu(int x,int y){to[++gr]=y,nxt[gr]=h[x],h[x]=gr;}
int n,m,mp[N][N],p[N],tot;
ll ans,dp[18][18],tmp;
void dfs(int u,int f){
    rep(j,1,tot)dp[u][j]=1;
    for(int i=h[u];i;i=nxt[i]){
        int d=to[i];
        if(d==f)continue;
        dfs(d,u);
        rep(j,1,tot){
            tmp=0;
            rep(k,1,tot){
                if(mp[p[j]][p[k]]) tmp+=dp[d][k];
            }
            dp[u][j]*=tmp;
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    int a,b;
    rep(i,1,m)scanf("%d%d",&a,&b),mp[a][b]=mp[b][a]=1;
    rep(i,1,n-1)scanf("%d%d",&a,&b),tu(a,b),tu(b,a);
    rep(j,1,(1<<n)-1){tot=0;
        rep(i,0,n-1){
            if((j>>i)&1)p[++tot]=i+1;
        }
        dfs(1,0);tmp=0;
        rep(i,1,tot)tmp+=dp[1][i];
        ans+=(((n-tot)&1)?-1ll:1ll)*tmp;
    }
    printf("%lld\n",ans);
    return 0;
}
相关文章
相关标签/搜索