bzoj4455&UOJ185 [Zjoi2016]小星星(樹形DP+容斥原理)

bzoj4455&UOJ185 [Zjoi2016]小星星

原題地址
http://www.lydsy.com/JudgeOnline/problem.php?id=4455
http://uoj.ac/problem/185

題意:
給你一個n 個點m 條邊的無向圖, 再給你一棵n 個點的樹, 問有多少種點編號的映射方式, 使得n 個點恰好匹配,且樹上的邊均存在於原圖中。

數據範圍
n<=17,m<=n*(n-1)/2

題解:
好題。
容易想到的樹形DP是:
n=17,狀壓。
DP[u][i][s]表示對於u節點及其子樹,u節點對應i點,整個子樹映射到s這個集合的方案數,
但是每次轉移都需要枚舉子集,複雜度 3^n*n^2無法承受。

有句話叫,計數問題考慮容斥。

那麼如果把嚴格的範圍放寬泛一點,考慮轉移複雜度更低的DP+容斥。
現在我們枚舉s,進行DP,
不要求嚴格是s內一一對應,可以有不同的點映射到同一個點,即對應的集合至多是s的方案數。
之後再做一次容斥即可。

代碼:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
const int N=20;
int n,m,g[N][N],head[N],to[2*N],nxt[2*N],num=0,cnt=0,q[N];
LL f[N][N],ans=0LL; 
void build(int u,int v)
{
    num++;
    to[num]=v;
    nxt[num]=head[u];
    head[u]=num;
}
inline int getnum(int s) 
{
    int ret=0;
    for(int i=0;i<n;i++) if(s&(1<<i)) {ret++; q[ret]=i+1;}
    return ret;
}
void dfs(int u,int fa)
{
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i]; if(v==fa) continue;
        dfs(v,u);
    }
    for(int i=1;i<=cnt;i++)
    {
        f[u][q[i]]=1; 
        for(int l=head[u];l;l=nxt[l])
        {
            LL cur=0;
            int v=to[l]; if(v==fa) continue;
            for(int j=1;j<=cnt;j++) if(g[q[i]][q[j]]) cur+=f[v][q[j]];
            f[u][q[i]]*=cur;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1,u,v;i<=m;i++) {scanf("%d%d",&u,&v); g[u][v]=g[v][u]=1;}
    for(int i=1,u,v;i<n;i++) {scanf("%d%d",&u,&v); build(u,v); build(v,u);}
    int top=1<<n;
    for(int s=1;s<top;s++)
    {
        cnt=getnum(s); LL w;
        if((cnt%2)==(n%2)) w=1LL; else w=-1LL;
        dfs(1,1);
        for(int i=1;i<=cnt;i++) ans+=w*f[1][q[i]];
    }
    printf("%lld\n",ans);
    return 0;
}
發佈了203 篇原創文章 · 獲贊 43 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章