码迷,mamicode.com
首页 > 其他好文 > 详细

树形dp 入门

时间:2018-10-25 00:31:51      阅读:191      评论:0      收藏:0      [点我收藏+]

标签:初始化   P20   树根   i++   转移   目的   任务   font   min   

今天学了树形dp,发现树形dp就是入门难一些,于是好心的我便立志要发一篇树形dp入门的博客了。

树形dp的概念什么的,相信大家都已经明白,这里就不再多说。直接上例题。

一、常规树形DP

P1352 没有上司的舞会

题目描述

某大学有N个职员,编号为1~N。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

 

这是一道非常经典的树形dp,首先分析题目,如果上司去了,那么他的所有下司都不能去;如果上司不去,那么他的所有下司去不去无所谓,但要取最大值。接下来就是状态转移方程:

我们设f[i][0]为i不去的情况,f[i][1]为i去的情况,那么

f[i][0]+=max(f[i的下司][1],f[i的下司][0]);
f[i][1]+=f[i的下司]][0];

初始化,如果i去,那么显然f[i][1]=i的快乐指数,如果i不去那么f[i][1]=0

最后贴上代码

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cmath>
 4 #include<string>
 5 #include<cstdio>
 6 using namespace std;
 7 struct edge
 8 {
 9     int num,child[6001];
10 }g[6001];
11 int n;
12 int a[10000];
13 int tree[10000];
14 int f[6001][2];
15 int aa,bb,root;
16 void dp(int t)
17 {
18     f[t][1]=a[t];//初始化 
19     f[t][0]=0;//初始化 
20     for(int i=1;i<=g[t].num;i++)
21     {
22         dp(g[t].child[i]);
23         f[t][0]+=max(f[g[t].child[i]][1],f[g[t].child[i]][0]);//状态转移 
24         f[t][1]+=f[g[t].child[i]][0];//状态转移
25     }
26 }
27 int main()
28 {
29     cin>>n;
30     for(int i=1;i<=n;i++)
31     {
32         scanf("%d",&a[i]);
33     }
34     while(scanf("%d%d",&aa,&bb))
35     {
36         if(aa==0&&bb==0) break;
37         g[bb].num++;//记录孩子结点的个数 
38         g[bb].child[g[bb].num]=aa;//储存孩子结点 
39         tree[aa]=bb;//记录父亲结点 
40     }
41     root=1;
42     while(tree[root]) root++;//找出树根 
43     dp(root);//从树根开始dp 
44     int ans=max(f[root][1],f[root][0]);
45     cout<<ans;
46     return 0;
47 }

洛谷P2016 战略游戏

题目描述

Bob喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。

他要建立一个古城堡,城堡中的路形成一棵树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了望到所有的路。

注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被了望到。

请你编一程序,给定一树,帮Bob计算出他需要放置最少的士兵.

 

和上个题一模一样,就是初始化不同,上个题每个人都有不同的权值,而这个题每个人的权值都为1;

状态转移一模一样 ;初始化,f[i][1]=1,f[i][1]=0;

最后上代码

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<string>
 5 #include<cstring>
 6 using namespace std;
 7 struct edge
 8 {
 9     int num,child[1501];
10 }g[1501];
11 int a[1501];
12 int f[1501][2];
13 int x,y;
14 int n;
15 void dp(int t)
16 {
17     f[t][1]=1;//初始化为1 
18     f[t][0]=0;
19     for(int i=1;i<=g[t].num;i++)
20     {
21         dp(g[t].child[i]);
22         f[t][0]+=f[g[t].child[i]][1];//状态转移 
23         f[t][1]+=min(f[g[t].child[i]][1],f[g[t].child[i]][0]);//状态转移 
24     }
25 }
26 int main()
27 {
28     cin>>n;
29     for(int i=1;i<=n;i++)
30     {
31         scanf("%d",&x);
32         scanf("%d",&g[x].num);//记录该结点的孩子个数 
33         for(int j=1;j<=g[x].num;j++)
34         {
35             scanf("%d",&g[x].child[j]);//存储该结点的孩子 
36             a[j]=i;//记录父亲 
37         }
38     }
39     int root=0;
40     while(a[root]) root++;//找出根节点 
41     dp(root);
42     int ans=min(f[root][1],f[root][0]);
43     cout<<ans;
44     return 0;
45 }

洛谷P2458 [SDOI2006]保安站岗

题目描述

五一来临,某地下超市为了便于疏通和指挥密集的人员和车辆,以免造成超市内的混乱和拥挤,准备临时从外单位调用部分保安来维持交通秩序。

已知整个地下超市的所有通道呈一棵树的形状;某些通道之间可以互相望见。总经理要求所有通道的每个端点(树的顶点)都要有人全天候看守,在不同的通道端点安排保安所需的费用不同。

一个保安一旦站在某个通道的其中一个端点,那么他除了能看守住他所站的那个端点,也能看到这个通道的另一个端点,所以一个保安可能同时能看守住多个端点(树的结点),因此没有必要在每个通道的端点都安排保安。

编程任务:

请你帮助超市经理策划安排,在能看守全部通道端点的前提下,使得花费的经费最少。

 

难度提升,如果看不懂可以先放一放

分析:

我们发现,要使所有点最终全部被覆盖,那么无非有3种状态:

(以下全部都是对于要覆盖任意一个以x为根的子树来说的)
(其中我们设y节点为y的儿子,fa为x的父亲)
1.x节点被自己覆盖,即选择x点来覆盖x点

2.x节点被儿子y覆盖,即选择y点来覆盖x点

3.x节点被父亲fa覆盖,即选择fa点来覆盖x点

借此三种状态,我们可以设f[x][0/1/2]为让以x为根的子树中的节点全部被覆盖,且x点的被覆盖情况为1/2/3时的最小代价

为了方便,我们不妨设这三种情况分别为:

1.f[x][0]---对应上面的1

2.f[x][1]---对应上面的2

3.f[x][2]---对应上面的3

既然是DP,总是有转移方程的,我们想一下dp方程要如何设计

设计状态转移方程:

(1):对应上面的1.

f[x][0]=∑ min(f[y][0],f[y][1],f[y][2]) + val[x]

其中val[x]是选择x点的代价

我们很容易想到,在节点x被选择之后,我们就可以无拘无束了(蛤?),也就是说对于x儿子节点y的状态可以不去考虑,因为x节点被选择之后y节点无论如何也会被覆盖到,所以我们在儿子y的所有状态里取min,累加起来就行了

(2):对应上面的3(先讲3,因为2比较难以理解,放到了后面)

f[x][2]=∑ min(f[y][0],f[y][1])

为什么3情况对应的转移方程要这样写呢?

我们不妨这样理解,对于x节点我们让它的父亲节点fa覆盖它,那么根据我们的状态设计,此时必须要满足以x的儿子y为根的子树之中所有点已经被覆盖

那么这时就转化为一个子问题,要让y子树满足条件,只有两种决策:要么y被y的儿子覆盖,要么被y自己覆盖(即选择y节点),只需要在y的这两种状态取min累加就可以了

(3):对应上面的2(DuangDuangDuang 敲黑板划重点啦)

f[x][1]=∑ min(f[y][0],f[y][1]),如果选择的全部都是f[y][1],要再加上min(f[y][0]-f[y][1])

到了这里,我们就要回顾一下我们设计的dp状态了:

设f[x][0/1/2]为让以x为根的子树中的节点全部被覆盖,且x点的被覆盖情况为1/2/3时的最小代价

先提示一下,如果你理解了下面,那么本题是很简单的。。如果你没理解,就返回到这里再看一遍吧,我就在这里等着你

对于此时的状态,f[x][1]代表对于节点x让x被自己的儿子覆盖,那么和分析(2)一样,都要先满足此时以y的子树已经满足了条件,才能进行转移,这就是前面那部分:∑ min(f[y][0],f[y][1])的来历,那么后面那一长串又是怎么回事呢?

我们可以这样理解,此时既然要保证x点是被自己的儿子覆盖的,那么如果此时y子树已经满足了全部被覆盖,但是y此时被覆盖的状态却是通过y节点自己的儿子达到的,那么x就没有被儿子y覆盖到,那么我们不妨推广一下,如果x所有的儿子y所做的决策都不是通过选择y点来满足条件,那么我们就必须要选择x的一个子节点y,其中y满足f[y][0]-f[y][1]最小,并把这个最小的差值累加到f[x][1]中去,这样才能使得x点被自己的儿子覆盖,状态f[x][1]也才能合理地得到转移

好了,如果你还是没有太懂这个(3)的设计过程,请你回到之前再仔细看几遍

如果你已经理解了上面,那么恭喜你这个题,你已经A掉了

因为转移方程既然有了,那么我们就只需要最后的答案了

由于题目中没有说这棵树的根节点是哪个,所以你可以默认1就是根,或者开一个数组在加边的时候记录一下每个点的入度,最后没有入度的点就是根(但好像没有区别,毕竟我A掉了)

最后答案即为min(f[root][0],f[root][1])

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<string>
 5 #include<cstring>
 6 using namespace std;
 7 struct edge
 8 {
 9     int num,child[6001];
10 }g[6001];
11 int a[6001];
12 int dp[6001][3];
13 int root[6001];
14 int n,aa;
15 void dfs(int x)
16 {
17     dp[x][0]=a[x];
18     int cc=1000000000;
19     int num=0;
20     for(int i=1;i<=g[x].num;i++)
21     {
22         dfs(g[x].child[i]);
23         int t=min(dp[g[x].child[i]][0],dp[g[x].child[i]][1]);
24         dp[x][0]+=min(t,dp[g[x].child[i]][2]);
25         dp[x][1]+=t;
26         dp[x][2]+=t;
27         if(dp[g[x].child[i]][0]<dp[g[x].child[i]][1]) num++;
28         else cc=min(cc,dp[g[x].child[i]][0]-dp[g[x].child[i]][1]);
29     }
30     if(num==0) dp[x][1]+=cc;
31 }
32 int main()
33 {
34     scanf("%d",&n);
35     for(int i=1;i<=n;i++)
36     {
37         scanf("%d",&aa);
38         scanf("%d",&a[aa]);
39         scanf("%d",&g[aa].num);
40         for(int j=1;j<=g[aa].num;j++)
41         {
42             scanf("%d",&g[aa].child[j]);
43             root[g[aa].child[j]]=aa;
44         }
45     }
46     aa=1;
47     while(root[aa]) aa++;
48     dfs(aa);
49     cout<<min(dp[aa][0],dp[aa][1]);
50     return 0;
51 }

二、树形背包问题(在树上进行分组背包处理)

未完待续~~~

树形dp 入门

标签:初始化   P20   树根   i++   转移   目的   任务   font   min   

原文地址:https://www.cnblogs.com/snowy2002/p/9846502.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!