1. 程式人生 > >【BZOJ4455】小星星(動態規劃,容斥)

【BZOJ4455】小星星(動態規劃,容斥)

之間 lld algorithm std 還需要 tchar 一次 lin 還需

【BZOJ4455】小星星(動態規劃,容斥)

題面

BZOJ
洛谷
Uoj

題解

題意說簡單點就是給定一張\(n\)個點的圖和一棵\(n\)個點的樹,現在要讓圖和樹之間的點一一對應,並且如果樹上存在一條邊,那麽圖上對應的點對之間也要存在邊。

我們直接求解顯然很麻煩,一一對應是一個很不好算的東西。
那麽我們先要求並不需要一一對應,隨意對應即可,最後再減掉不合法的方案,這樣就可以用容斥來解決。
怎麽容斥呢?無非是考慮沒有一一對應的關系,那麽我們先暴力枚舉一下哪些點在圖上可以和樹上的點進行對應,其他的點不能夠和樹上的點進行匹配。
那麽考慮\(dp\)計算方案數。
\(f[i][j]\)表示當前以\(i\)

為根的子樹(假裝以\(1\)號點為根節點的有根樹),並且\(i\)在圖上對應的點是\(j\)的方案數。
每次暴力選擇一個和當前\(i\)匹配的點,然後再暴力找到這個點在圖中的所有兒子,並且用子樹進行轉移,這樣\(dp\)一次的復雜度是\(O(n\times n\times n)\),即樹上每個點都要做一次,要暴力枚舉和哪個點進行匹配,還需要暴力枚舉兒子是哪個點,當然這樣肯定不滿。
再加上暴力枚舉可以進行匹配的點集的枚舉,
所以總的時間復雜度是\(O(n^32^n)\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define ll long long
#define MAX 20
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
struct Line{int v,next;}e1[MAX*MAX<<1],e2[MAX<<1];
int h1[MAX],h2[MAX];
int cnt1=1,cnt2=1;
int n,m,cnt,a[MAX];
inline void Add1(int u,int v){e1[cnt1]=(Line){v,h1[u]};h1[u]=cnt1++;}
inline void Add2(int u,int v){e2[cnt2]=(Line){v,h2[u]};h2[u]=cnt2++;}
ll f[MAX][MAX],ans=0,num;
void dfs(int u,int ff)
{
    for(int i=h2[u];i;i=e2[i].next)
        if(e2[i].v!=ff)dfs(e2[i].v,u);
    for(int i=1;i<=cnt;++i)//枚舉當前點得到匹配點
    {
        f[u][a[i]]=1;
        for(int j=h2[u];j;j=e2[j].next)
            if(e2[j].v!=ff)
            {
                num=0;
                for(int k=h1[a[i]];k;k=e1[k].next)num+=f[e2[j].v][e1[k].v];
                f[u][a[i]]*=num;
                if(!f[u][a[i]])break;
            }
    }
}
int main()
{
    n=read();m=read();
    for(int i=1,u,v;i<=m;++i)u=read(),v=read(),Add1(u,v),Add1(v,u);
    for(int i=2,u,v;i<=n;++i)u=read(),v=read(),Add2(u,v),Add2(v,u);
    for(int i=1;i<(1<<n);++i)//枚舉可以進行匹配的點集
    {
        cnt=0;memset(f,0,sizeof(f));
        for(int j=0;j<n;++j)if(i&(1<<j))a[++cnt]=j+1;
        dfs(1,0);num=0;
        for(int j=1;j<=cnt;++j)num+=f[1][a[j]];
        if((n-cnt)&1)ans-=num;else ans+=num;
    }
    printf("%lld\n",ans);
    return 0;
}

【BZOJ4455】小星星(動態規劃,容斥)