哈,树形DP专题来的
树形dp,一般是把树用dfs遍历一遍,然后根据子节点的状态转移出根节点的状态
0 【CQOI2009】叶子的颜色【推荐】
给一棵m个结点的无根树,你可以选择一个度数大于1的结点作为根,然后给一些结点(根、内部
结点和叶子均可)着以黑色或白色。你的着色方案应该保证根结点到每个叶子的简单路径上都至少
包含一个有色结点(哪怕是这个叶子本身)。
对于每个叶结点u,定义c[u]为从u到根结点的简单路径上第一个有色结点的颜色。给出每个c[u]的值,设计着色方案,使得着色结点的个数尽量少。
题目中任何非叶子节点为根得出的答案都一样,所以随便选一个根就好
设f[i][1/0]表示以i为根的子树中,i染成1/0(黑色或白色)时,最少的着色节点数
首先,每个节点的初始值为1,因为染色后代价为1
若i染成黑色,儿子也是黑色,那么f[i][1]+=f[son][1]-1,结果要加起来,因为父亲节点的代价应为每个
子节点的和,若不同色,那么直接把子节点的代价加入父亲节点
#include <cstdio>
#include <algorithm>
using namespace std;
int n,m,cnt,c[100005],f[100005][2];
int ls[100005],ne[100005],jy[100005];
void init(int x,int y){
ne[++cnt]=ls[x];ls[x]=cnt;jy[cnt]=y;
}
void dfs(int x,int fa){
int t=ls[x];
if (!f[x][0]) f[x][0]=f[x][1]=1;
while (t){
if (jy[t]!=fa){
dfs(jy[t],x);
f[x][0]+=min(f[jy[t]][0]-1,f[jy[t]][1]);
f[x][1]+=min(f[jy[t]][1]-1,f[jy[t]][0]);
}
t=ne[t];
}
}
int main(){
scanf("%d%d",&m,&n);
for (int i=1;i<=n;i++){
int e;
scanf("%d",&e);
f[i][e]=1;f[i][e^1]=1000000;
}
for (int i=1;i<m;i++){
int x,y;
scanf("%d%d",&x,&y);
init(x,y);init(y,x);
}
dfs(m,0);
printf("%d",min(f[m][1],f[m][0]));
}
1 【NOI2002】贪吃的九头龙
传说中的九头龙是一种特别贪吃的动物。虽然名字叫“九头龙”,但这只是说它出生的时候有九个头,而在成长的过程中,它有时会长出很多的新头,头的总数会远大于九,当然也会有旧头因衰老而自己脱落。
有一天,有M个脑袋的九头龙看到一棵长有N个果子的果树,喜出望外,恨不得一口把它全部吃掉。可是必须照顾到每个头,因此它需要把N个果子分成M组,每组至少有一个果子,让每个头吃一组。
这M个脑袋中有一个最大,称为“大头”,是众头之首,它要吃掉恰好K个果子,而且K个果子中理所当然地应该包括唯一的一个最大的果子。果子由N-1根树枝连接起来,由于果树是一个整体,因此可以从任意一个果子出发沿着树枝“走到”任何一个其他的果子。
对于每段树枝,如果它所连接的两个果子需要由不同的头来吃掉,那么两个头会共同把树枝弄断而把果子分开;如果这两个果子是由同一个头来吃掉,那么这个头会懒得把它弄断而直接把果子连同树枝一起吃掉。当然,吃树枝并不是很舒服的,因此每段树枝都有一个吃下去的“难受值”,而九头龙的难受值就是所有头吃掉的树枝的“难受值”之和。
九头龙希望它的“难受值”尽量小,你能帮它算算吗?
例如图1所示的例子中,果树包含8个果子,7段树枝,各段树枝的“难受值”标记在了树枝的旁边。九头龙有两个脑袋,大头需要吃掉4个果子,其中必须包含最大的果子。即N=8,M=2,K=4:
当m>=3时,总难受值就是大头的难受值,因为剩下的最少也有两个头,每段树枝都可以分给两个头
各吃一边;当m=2时,如果大头不吃就只能分给小头,所以当大头有连续的两个果子不吃时,连接两
个果子的树枝的难受值也应计入答案。而-1的情况很好判断。
设f[i][j][1/0]表示第i个点,吃了j个果子,当前果子吃不吃(1/0)的难受值
由于方程中出现了”–”这种东西,所以要么j倒着循环,要么重开一个数组记录f[i]的值,用这个数组更新答案
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
int n,m,cnt,k;
int f[610][610][2],w[610][610];
int ne[610],ls[610],jy[610];
void dfs(int x,int fa){
f[x][1][1]=f[x][0][0]=0;
for (int t=ls[x];t;t=ne[t])
if (jy[t]!=fa){
int y=jy[t];
dfs(y,x);
int ff[310][2];
memcpy(ff,f[x],sizeof(ff));
memset(f[x],0x3f,sizeof(f[x]));
for (int i=0;i<=k;i++){
for (int j=0;j<=k&&j<=i;j++){
f[x][i][1]=min(f[x][i][1],
min(f[y][j][1]+ff[i-j][1]+w[x][y],f[y][j][0]+ff[i-j][1]));
f[x][i][0]=min(f[x][i][0],
min(f[y][j][1]+ff[i-j][0],f[y][j][0]+ff[i-j][0]+w[x][y]*(m==2)));
}
}
}
}
void init(int x,int y){
ne[++cnt]=ls[x];ls[x]=cnt;jy[cnt]=y;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
if (n-k<m-1) {printf("-1");return 0;}
for (int i=1;i<=n-1;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
init(a,b);init(b,a);
w[a][b]=w[b][a]=c;
}
memset(f,0x3f,sizeof(f));
dfs(1,0);
printf("%d",f[1][k][1]);
}
2 【GDOI2005】寻宝之旅【难】
探险队长凯因意外的弄到了一份黑暗森林的藏宝图,于是,探险队一行人便踏上了寻宝之旅,去寻找传说中的宝藏。
藏宝点分布在黑暗森林的各处,每个点有一个值,表示藏宝的价值。它们之间由一些小路相连,小路不会形成环,即两个宝藏点之间有且只有一条通路。探险队从其中的一点出发,每次他们可以留一个人在此点开采宝藏,也可以不留,然后其余的人可以分成若干队向这一点相邻的点走去。需要注意的是,如果他们把队伍分成两队或两队以上,就必须留一个人在当前点,提供联络和通讯,当然这个人也可以一边开采此地的宝藏。并且,为了节约时间,队伍在前往开采宝藏的过程中是不会走回头路的。现在你作为队长的助理,已经提供了这幅藏宝图,请你算出探险队所能开采的最大宝藏的价值。
设f[i][j][1/0]表示在第i个点,用了j个人,在当前点是否留一人开采宝藏(1/0),可获得的最大价值
一般这些用了j的代价的状态表示,都要给j两个循环,枚举当前节点代价和子节点代价
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
int n,m,cnt,a[118];
int f[118][118][2],bz[118];
int ls[218],ne[218],jy[218];
void dfs(int x,int fa){
for (int i=1;i<=m;i++)
f[x][i][1]=a[x];
for (int t=ls[x];t;t=ne[t])
if (jy[t]!=fa){
dfs(jy[t],x);
for (int j=1;j<=m;j++)
f[x][j][0]=max(f[x][j][0],max(f[jy[t]][j][0],f[jy[t]][j][1]));
for (int j=m;j>=1;j--)
for (int k=1;k<=j;k++)
f[x][j][1]=max(f[x][j][1],
f[x][k][1]+max(f[jy[t]][j-k][1],f[jy[t]][j-k][0]));
}
}
void init(int x,int y){
ne[++cnt]=ls[x];ls[x]=cnt;jy[cnt]=y;
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
init(x,y);init(y,x);
}
bz[1]=1;
dfs(1,0);
printf("%d",max(f[1][m][0],f[1][m][1]));
}