P1352 没有上司的舞会&&树形DP入门

https://www.luogu.com.cn/problem/P1352

题目描述

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

输入格式

第一行一个整数N。(1<=N<=6000)

接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128<=Ri<=127)

接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。

最后一行输入0 0

输出格式

输出最大的快乐指数。

输入输出样例

输入 #1复制
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0
输出 #1复制
5
样例分析:

P1352 没有上司的舞会&&树形DP入门_第1张图片

 

根节点为5,那么3,4不去,就可以获得最大快乐值=5


思路:可用树形DP或者拓扑排序来做
一开始想到的可能是用一个一维数组dp[i]表示在第i个人的位置能获得的最大快乐,但是这个位置上的人去或者不去,都会对下属有影响,具有后效性,
比如说dp[2]作为根节点,那么他的最优解肯定是这个节点的快乐值,如果3要参加,则dp[3]无法继承dp[2]的最优解

那么我们可以在这个基础上增加一维,用来1/0表示这个人去还是不去
如果不去,那么他的直接下属都可以去,dp[i][0]=sum((
max(dp[son][1],dp[son][0])) son表示员工
若去,则他的直接下属都不能去,dp[i][1]=sum(dp[son][0]);
 

那什么是后效性?

无后效性,有两层含义。

第一层含义是,在推导后面阶段状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步步推导出来的。

第二层含义是,某阶段状态一旦确定,就不受之后阶段的决策影响。无后效性是一个非常“宽松”的要求。只要满足前面提到的动态规划问题模型,其实基本上都会满足无后效性。

---来源CSDN博客

简单些的例子:迷宫问题中,假设你走到了(n,m)点,之后的状态转移不会再关心你是如何走到(n,m)点的,只关心你在(n,m)点的状态信息(例如耗费)。

之后发生的不会影响之前的结果。拿最长公共子序列来说,你在后面碰到的字符不会影响你前面字符的匹配数量和结果,每次增加匹配到的字符时,都是“继承”前面的结果之后加一。所以如果后面的字符如果能改变前面的字符,那么我们存状态意义就不大了。就是因为有大量重复计算在递归里,我们才用空间换时间,用了动态规划。如果状态总是变,那也没必要存了。每次都暴力算就行。---来源知乎某处(我忘了)

 


所以我们需要在这个基础上再加上一维,分别以0和1表示这个人去或者不去。
  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 #include
  8 #include
  9 #include
 10 using namespace std;
 11 typedef long long ll; 
 12 inline int read(){
 13     int X=0,w=0;char ch=0;
 14     while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
 15     while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
 16     return w?-X:X;
 17 }
 18 /*------------------------------------------------------------------------*/
 19 const int maxn=6005;
 20 int v[maxn],dp[maxn][2],fa[maxn]; 
 21 vector<int>son[maxn];
 22 int vis[maxn],tree[maxn];int cnt=0;
 23 void bfs(int root){
 24     
 25     queue<int>q;
 26     q.push(root);
 27     vis[root]=1;
 28     tree[++cnt]=root;
 29     
 30     while(!q.empty()){
 31         
 32         int now=q.front();q.pop();
 33         
 34         int len=son[now].size();
 35         
 36         for(int i=0;ii){
 37             
 38             if(!vis[son[now][i]]){
 39                 vis[son[now][i]]=1;
 40                 tree[++cnt]=son[now][i];
 41                 q.push(son[now][i]); 
 42             }
 43             
 44         }
 45         
 46         
 47     }
 48     
 49     
 50 }
 51 int main( )
 52 {    
 53     ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
 54     //freopen("a.txt","r",stdin);
 55     //freopen("a.txt","w",stdout);
 56     int n;
 57     cin>>n;
 58     for(int i=1;i1;++i){
 59         fa[i]=i;
 60         cin>>v[i];
 61         
 62     }
 63     for(int i=1;i<=n;++i){
 64         int u,v;
 65         cin>>u>>v;
 66         if(u==0)break;
 67         
 68         fa[u]=v;
 69         son[v].push_back(u);
 70         
 71         
 72     }
 73     
 74     int root = n;
 75     
 76     while(fa[root]!=root)root=fa[root];
 77     
 78     bfs(root);
 79     
 80     //从叶子节点开始dp 
 81     for(int i=cnt;i>0;--i){
 82         
 83         int now=tree[i];
 84         int len=son[now].size();
 85         
 86         for(int j=0;j//他的某一个下属 
 87             
 88             //1表示去 
 89             dp[now][0]+=max(dp[son[now][j]][0],dp[son[now][j]][1]);
 90             dp[now][1]+=dp[son[now][j]][0]; //如果now去,则now的下属不能去 
 91         }
 92          
 93         dp[now][1]+=v[now];
 94         //根节点,写在外面的原因是
 95         //叶子节点无法进入第二层循环 
 96         //并且叶子节点表示now去,所以二维状态是1 
 97         
 98     }
 99     
100     cout<<(max(dp[root][1],dp[root][0]))<<endl;
101     
102     
103     return 0;
104 }
拓扑排序的DP思想和上面的差不多,就是省略了建树的过程,因为拓扑排序的算法特殊性帮助我们完成了这一过程,根节点入度为0,
那么我们就可以不断地在排序过程中完成DP
 1 #include
 2 #include
 3 #include
 4 #include
 5 #include
 6 #include
 7 #include
 8 #include
 9 #include
10 #include
11 #include
12 using namespace std;
13 const int maxn=6005;
14 int a[maxn],du[maxn],dp[maxn][2];
15 int n;
16 
17 int main(){
18     
19     cin>>n;
20     
21     for(int i=1;i<=n;++i){
22         cin>>a[i];
23     }
24     vector<int>son[maxn];
25     int u,v;
26     while(cin>>u>>v&&u&&v){
27         
28         du[v]++;
29         
30         son[u].push_back(v);
31     }
32     
33     queue<int>q;
34     for(int i=1;i<=n;++i){
35         
36         if(!du[i]){//叶子节点 
37             q.push(i);
38             dp[i][1]=a[i];
39         }
40     }
41     map<int,int>mp;
42     int ans=0;
43     while(!q.empty()){
44         
45         int now=q.front();q.pop();
46         int len=son[now].size();
47         
48         
49         
50         for(int i=0;ii){
51             
52             int boss=son[now][i];
53             dp[boss][0]+=max(dp[now][0],dp[now][1]);
54             dp[boss][1]+=dp[now][0];
55             
56             
57             du[boss]--;
58             if(!du[boss]){
59                 dp[boss][1]+=a[boss];
60                 q.push(boss);
61                 mp[i]=1;
62             }
63             ans=max(dp[boss][1],dp[boss][0]);
64         }
65         
66     }
67     cout<endl;
68     return 0;
69 } 

发现我这个代码写的有点复杂。。。

参考了一位大佬的代码,和我的代码的区别是我用vector存每个节点的上司,那么判断过程中就需要每次从vector里面取出,其实我们只需要设置一个father数组用来存储就可以了

 1 #include
 2 #include
 3 #include
 4 #include
 5 #include
 6 #include
 7 #include
 8 #include
 9 #include
10 using namespace std;
11 typedef long long ll; 
12 inline int read(){
13     int X=0,w=0;char ch=0;
14     while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
15     while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
16     return w?-X:X;
17 }
18 /*------------------------------------------------------------------------*/
19 const int maxn=1005; 
20 int v[maxn],dp[maxn][2],father[maxn],du[maxn]; 
21 int vis[maxn],tree[maxn];int cnt=0;
22 int main( )
23 {    
24     ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
25     //freopen("a.txt","r",stdin);
26     //freopen("a.txt","w",stdout);
27     
28     int n;
29     cin>>n;
30     for(int i=1;i1;++i){
31         father[i]=i;
32         cin>>v[i];
33         
34     }
35     for(int i=1;i<=n;++i){
36         int u,v;
37         cin>>u>>v;
38         if(u==0)break;
39         
40         father[u]=v;
41         
42         du[v]++;
43         
44     }
45     queue<int>q;
46     for(int i=1;i<=n;++i){
47         if(!du[i]){
48             
49             q.push(i); 
50         }
51         
52     } 
53     int root;
54     while(!q.empty()){
55         
56         int now=q.front();q.pop();
57         root=now;
58         //上司不去 
59         dp[father[now]][0]=max(dp[now][0],dp[now][1]);
60         //
61         dp[father[now]][1]+=v[father[now]]+dp[now][0]; 
62         
63         du[father[now]]--;
64         if(!du[father[now]]){
65             q.push(father[now]);
66         }
67     }
68     cout<0],dp[root][1])<<endl;
69     
70     
71     return 0;
72 }

 




你可能感兴趣的:(P1352 没有上司的舞会&&树形DP入门)