顾名思义是树上的dp
例1:没有上司的舞会
题意:
有一场舞会,每一个人都有一个快乐值,但不能和上司同时出现在舞会中,问这个舞会的快乐值最大为多少。
每个人都有唯一的上司,除了根节点没有
状态
用dp[i][0] 表示i节点不去时的最优解
用dp[i][1] 表示i节点去时的最优解
状态转移方程
他要是不去,他儿子就可去可不去
dp[u][0]+=max(dp[v][0],dp[v][1]);
他要是去,他儿子就不能去
dp[u][1]+=dp[v][0];
例2:二叉苹果树
有一棵苹果树,苹果树的是一棵二叉树,共N个节点,树节点编号为1~N,编号为1的节点为树根,边可理解为树的分枝,每个分支都长着若干个苹果,现在要要求减去若干个分支,保留M个分支,要求这M个分支的苹果数量最多。
状态
f[u][j]表示在以u为根的子树保留j个分支可以得到的最大苹果数量
状态转移方程
f[u][k]=f[u][k-j]+f[v][j-1]+edge[i].w;
for(int k=m;k>=0;k++){
for(int j=1;j<=k;j++){
if(f[u][k]<f[u][k-j]+f[v][j-1]+edge[i].w;)
f[u][k]=f[u][k-j]+f[v][j-1]+edge[i].w;
}
}
比如当保留3个分支时(m==3)
那么他可以变成父亲保留2个分支 儿子不保留 加上父亲指向儿子的这条分支
也可以父亲保留1个分支 儿子保留一个分支 加上父亲指向儿子的这条分支
也可以父亲不保留 儿子保留两个分支 加上父亲指向儿子的这条分支 (其实这个实际上没有意义,因为父亲不保留,那么肯定是父亲之后的枝条全砍了,也就不存在儿子还有两个分支的情况了)
void dfs(int u,int fa){
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa){
dfs(v,u);
for(int k=m;k>=0;k--){
for(int j=1;j<=k;j++){
if(dp[u][k]<dp[u][k-j]+dp[v][j-1]+edge[i].w)
dp[u][k]=dp[u][k-j]+dp[v][j-1]+edge[i].w;
}
}
}
}
}
例3:Strategic game
一城堡的所有的道路形成一个n个节点的树,如果在一个节点上放上一个士兵,那么和这个节点相连的边就会被看守住,问把所有边看守住最少需要放多少士兵
树的最小点覆盖

状态
f[x][1]以x为根的子树在x上放置的士兵的最少所需的士兵数目
f[x][0]以x为根的子树x上不放置的士兵的最少所需的士兵数目
状态转移方程
f[x][1] =1 + Σ min(f[i][0],f[i][1])
x上放置的士兵,于是它的儿子们可放可不放!
f[x][0] = Σ f[i][1]
x上不放置的士兵,它的儿子们都必须放!
(i是x的儿子!!)
结果为min(f[root][0], f[root][1])
void dfs(int u,int fa){
dp[u][1]=1; //dp[x][1]以x为根的子树在x上放置的士兵的最少所需的士兵数目 但他可能没有儿子,所以先赋值个1
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa){
dfs(v,u);
dp[u][1]+=min(dp[v][0],dp[v][1]); //u放 儿子可放可不放
dp[u][0]+=dp[v][1]; //不放的话 它的儿子都必须放
}
}
}
树的最小支配集
给你一棵无向树,问你最少用多少个点可以覆盖掉所有其他的点。
(一个点被盖,它自己和与它相邻的点都算被覆盖)

状态:
1.dp[u][0]:选点u,并且以点i为根的子树都被覆盖了。
2.dp[u][1]:不选点u,u被其儿子覆盖
3.dp[u][2]:不选点u,u没有被子节点覆盖(被其父亲覆盖)
状态转移方程:
1.dp[u][0]=1+Σmin(dp[v][0],dp[v][1],dp[v][2])
2.dp[u][2]=Σ(dp[v][1]) (他被父亲覆盖,那他的儿子一定是被其儿子覆盖的)
3.dp[u][1]比较复杂(他的所有儿子里面必须有一个取dp[u][1])
那么:if(u没有子节点) dp[u][1]=INF
else dp[u][1]=Σmin(dp[v][0],dp[v][1])+inc
对于inc有:
if(上面式子中的Σmin(dp[v][0],dp[v][1])中包含某个dp[v][0]) inc=0;
else inc=min(dp[v][0]-dp[v][1])
void dfs(int u,int fa){
dp[u][0] = 1;
dp[u][1] = 0;
dp[u][2] = 0;
int sum = 0, inc = INF;
bool judge = false;
for (int i = head[u]; ~i;i=edge[i].next){
int v = edge[i].v;
if(v!=fa){
dfs(v, u);
dp[u][0] += min(dp[v][0], min(dp[v][1], dp[v][2]));
if(dp[v][0]<=dp[v][1]){ //存在 取儿子 小于 儿子被其儿子覆盖
sum += dp[v][0];
judge = true;
}else{
sum += dp[v][1];
inc = min(inc, dp[v][0] - dp[v][1];)
}
if(dp[v][1]!=INF&&dp[u][2]!=INF) //当前结点被父亲覆盖,儿子节点都被儿子覆盖,当前结点才能被父亲覆盖
dp[u][2] += dp[v][1];
else
{
dp[u][2] = INF;
}
dp[u][1] += min(dp[v][0], dp[v][1]);
}
}
if(inc==INF&&!judge) //u没有儿子
dp[u][1] = INF;
else{
dp[u][1] = sum; //inc=0
if(!judge)
dp[u][1] += inc; //inc= inc = min(inc, dp[v][0] - dp[v][1];)
}
}
628

被折叠的 条评论
为什么被折叠?



