例題1
題意
(洛谷4084 [USACO17DEC]Barn Painting)
給定一顆N個節點組成的樹,3種顏色,其中K個節點已染色,要求任意兩相鄰節點顏色不同,求合法染色方案數。
題解
樹形計數類DP
簡單粗暴的,設f[x][t]表示將x染色爲t時,x這棵子樹的方案數。
那麼就有,
初始化f[x][t]=1,特別的,當x已被染色爲t時,f[x][k]=0(k!=t)。
答案爲
小結
樹上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這個點的方案數。
轉移也很像,
當前點集,總方案爲,即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;
}