例题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;
}