樹上方案數統計—總結

例題1

題意

(洛谷4084 [USACO17DEC]Barn Painting)
給定一顆N個節點組成的樹,3種顏色,其中K個節點已染色,要求任意兩相鄰節點顏色不同,求合法染色方案數。

題解

樹形計數類DP
簡單粗暴的,設f[x][t]表示將x染色爲t時,x這棵子樹的方案數。

那麼就有f[x][t]=\prod _{y\in son[x]} \sum_{k\neq t}f[y][k]

初始化f[x][t]=1,特別的,當x已被染色爲t時,f[x][k]=0(k!=t)。
答案爲\sum_{i=0}^{2} f[x][i]

小結

樹上DP求方案數的特點是對於x求出不包含x的子樹的方案,因爲子樹間互不相干,所以將x的子節點的子樹 所有方案之和 乘起來就是 x子樹的方案數 了。對於x=root,就是我們要求的總方案數。請注意其中加法原理和乘法原理的穿插運用。

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int MAXN=1e5+10;

int n,K;
int a[MAXN];

struct E{int y,next;}e[MAXN*2];int len=1,last[MAXN];
void ins(int x,int y)
{
    e[++len]=(E){y,last[x]};last[x]=len;
}

ll f[MAXN][3];
void dfs(int x,int fa)
{
    if(a[x]==0) f[x][0]=f[x][1]=f[x][2]=1;
    else f[x][0]=f[x][1]=f[x][2]=0,f[x][a[x]-1]=1;
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(y==fa) continue;
        dfs(y,x);
        for(int i=0;i<3;i++) f[x][i]=f[x][i]*(f[y][(i+1)%3]+f[y][(i+2)%3])%mod;
    }
}

int main()
{
    scanf("%d%d",&n,&K);
    for(int i=1;i<n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        ins(x,y);ins(y,x);
    }
    for(int i=1;i<=K;i++)
    {
        int x,c;scanf("%d%d",&x,&c);
        a[x]=c;
    }
    dfs(1,0);
    printf("%lld\n",(f[1][0]+f[1][1]+f[1][2])%mod);
    return 0;
}


例題2

題目

(洛谷3349 [ZJOI2016]小星星)
小Y是一個心靈手巧的女孩子,她喜歡手工製作一些小飾品。她有n顆小星星,用m條彩色的細線串了起來,每條細線連着兩顆小星星。
有一天她發現,她的飾品被破壞了,很多細線都被拆掉了。這個飾品只剩下了n?1條細線,但通過這些細線,這顆小星星還是被串在一起,也就是這些小星星通過這些細線形成了樹。小Y找到了這個飾品的設計圖紙,她想知道現在飾品中的小星星對應着原來圖紙上的哪些小星星。如果現在飾品中兩顆小星星有細線相連,那麼要求對應的小星星原來的圖紙上也有細線相連。小Y想知道有多少種可能的對應方式。
只有你告訴了她正確的答案,她纔會把小飾品做爲禮物送給你呢。

題解

樹形計數類DP+容斥
老套路設f[x][u]表示x子樹中,x映射到原圖中u這個點的方案數。

轉移也很像,f[x][u]=\prod_{y\in x}\sum_{v\subseteq u} f[y][u]*ma[u][v]

當前點集,總方案爲\sum_{i=1}^{n} f[root][i],即root映射到任意點的方案數之和。

意思是x對應u點,y對應v點,如果u和v相連,那麼這一部分可以算爲一種方案。
容易發現狀態是重複計算了的,所以要容斥一下。枚舉點集的所有狀態,記錄點集大小,如果與n的差爲偶則加,爲奇則減。
這題很卡常,如果T了一定要參考代碼實現。主要是要省掉統計點集大小這一步,建議用dfs來枚舉所有狀態。

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=20;

inline int read()
{
    int re=0;char ch=getchar();
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9') re=re*10+(ch^48),ch=getchar();
    return re;
}

int n,m,ma[MAXN][MAXN];

struct E{int y,next;}e[MAXN*2];int len=1,last[MAXN];
void ins(int x,int y)
{
    e[++len]=(E){y,last[x]};last[x]=len;
}

int num[MAXN];int tot=0,ve[MAXN];
ll ans,f[MAXN][MAXN];
inline void dfs(int x,int fa)
{
    for(int i=1;i<=n;i++) f[x][i]=1;
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(y==fa) continue;
        dfs(y,x);
        for(int i=1,u=ve[1];i<=tot;u=ve[++i])
        {
            ll sum=0;//公式往這裏套
            for(int j=1,v=ve[1];j<=tot;v=ve[++j]) if(ma[u][v]) sum+=f[y][v];
            f[x][u]*=sum;
        }
    }
}

inline void work(int k,int cnt)
{
    if(k>n)
    {
        dfs(1,0);
        ll sum=0;
        for(int i=1;i<=n;i++) sum+=f[1][i];
        if(n-cnt&1) ans-=sum;else ans+=sum;
    }
    else
    {
        num[k]=0;work(k+1,cnt);
        num[k]=1;ve[++tot]=k;work(k+1,cnt+1);tot--;//ve記錄點集中的點
    }
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        ma[x][y]=ma[y][x]=1;
    }
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        ins(x,y);ins(y,x);
    }
    /*for(int i=1,imax=1<<n;i<imax;i++)
    {
        int cnt=0;ll sum=0;
        for(int j=1,k=i;j<=n;j++,k>>=1) num[j]=k&1,cnt+=num[j];//這種做法相當慢
        dfs(1,0);
        for(int j=1;j<=n;j++) sum+=f[1][j];
        if(n-cnt&1) ans-=sum;else ans+=sum;
    }*/
    work(1,0);
    printf("%lld\n",ans);
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章