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
输出格式
输出最大的快乐指数。
输入输出样例
7 1 1 1 1 1 1 1 1 3 2 3 6 4 7 4 4 5 3 5 0 0
5
样例分析:
根节点为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 #include2 #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;i i){ 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;i 1;++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 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include
发现我这个代码写的有点复杂。。。
参考了一位大佬的代码,和我的代码的区别是我用vector存每个节点的上司,那么判断过程中就需要每次从vector里面取出,其实我们只需要设置一个father数组用来存储就可以了
1 #include2 #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;i 1;++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 }