参考资料:
《国际大学生程序设计竞赛例题解四》
有这本书的同学可以直接忽略一下内容
传送门
Description
探险队长凯因意外的弄到了一份黑暗森林的藏宝图,于是,探险队一行人便踏上了寻宝之旅,去寻找传说中的宝藏。
藏 宝点分布在黑暗森林的各处,每个点有一个值,表示藏宝的价值。它们之间由一些小路相连,小路不会形成环,即两个藏宝点之间有且仅有一条通路。探险队从其中 的一点出发,每次他们可以留一个人在此点开采宝藏,也可以不留,然后其余的人可以分成若干队向这一点相邻的点走去。需要注意的是,如果他们把队伍分成两队 或者两队以上,就必须留一个人在当前点,提供联络和通讯,当然这个人也可以一边开采此地的宝藏。并且,为了节约时间,队伍在前往开采宝藏的过程中是不会走 回头路的。现在你作为队长的助理,已经提供了这幅藏宝图,请你算出探险队所能开采的最大宝藏价值。
Input
第一行有两个正整数n(1≤n≤100)表示藏宝点的个数,m(1≤m≤100)表示探险队的人数。
第二行是n个不超过100的正整数,分别表示1到n每个点的宝藏价值。
接下来的n-1行,每行两个数,x和y(1≤x,y≤n,x≠y),表示藏宝点x,y之间有一条路,数据保证不会有重复的路出现。
假设一开始探险队在点1处。
Output
一个整数,表示探险队所能获得最大的宝藏价值。
Sample Input
Copy sample input to clipboard
5 31 3 7 2 81 22 31 44 5
Sample Output
16
题解:
算法思想及主要用到的数据结构这是一道树形结构的动态规划题目
首先根据提议中的路径不成环可以知道此图形是树结构
题 目中说将m个人放置在藏宝点进行宝藏的发掘,也就是说题目可以转化为在n个点钟选择m个点,使得m个点的权值之和最大,题目中说道“如果他们把队伍分成两 队或者两队以上,就必须留一个人在当前点,提供联络和通讯,当然这个人也可以一边开采此地的宝藏。”也就是说如果选择了一棵子树的两棵或者两棵一上的自树 那么这棵子树的树根也要被选择
比如:
1
/ \
2 3
如果我们选择开采2号和3号的宝藏,那么我们就必须也开采1号的宝藏
先来看一个简单的情况
如果没有上述的限制条件,我们可以使用ans[i][j]来表示以i为根的自树在上面选择了j个节点时候的最大权值,这样ans[i][j]实际上就可以表示为
ans[i][j]=max{ans[i1][k1]+...+ans[in][kn]}
其中in都为自树的根,k1+。。。+kn=j。ans[in][kn]就表示在以in为根节点的自树上选取kn个节点的最大权值
当加上题目中的限制条件之后情况变的复杂,但是实际的思想是没有改变的
我们可以将动态规划的情况在进行细分,也就是分为选择根节点i和不选择根节点i两种情况
ans[i][0][m]表示不选取根节点i的最大权值
ans[i][1][m]表示选择根节点i的最大权值
那么我们就可以写出如下的状态转移方程
ans[i][0][m]=max{ans[i1][1 or 0][m]......ans[in][1 or 0][m]}
ans[i][1][m]=i的权值+max{ans[i1][1 or 0][k1]+......+ans[in][1 or 0][kn]}
其中k1+。。。+kn=m-1
关于第一个方程
因为我们没有选择根节点i所以只能在i的一颗自树上选择m个点来得到最大权值,所以应该是比较i的所有孩子节点选择m个节点时的最大值从中选出最大的。
关于第二个方程
因为选择了根节点i所以我们可以在他的所有自树中任意的选m-1个点(根节点i用去了一个点),这是就变成我们之前说的没有限制条件的情况
其中ans[i][0][0]=0,ans[i][1][1]=i的权值
两个方程的实现
第一个方程直接遍历所有的孩子节点找出最大值将它赋值给根节点即可
第二个方程我们可以从孩子节点逐渐向上更新父节点的取值情况,如果从子树中选择k1个节点的权值跟从根节点选择k2个节点的权值之和大于从根节点选取k1+k2个节点的权值,那么更新根节点选取k1+k2个节点的权值即
if(ans[in][0 or 1][k1]+ans[i][1][k2]>ans[i][1][k1+k2])
ans[i][1][k1+k2]=ans[in][0 or 1][k1]+ans[i][1][k2];
主要使用的算法如下:
动态规划
广度优先搜索
主要使用的数据结构
树
2.算法描述
1 #include <iostream>
2 #include <memory.h>
3 #include <queue>
4 using namespace std;
5 int vis[110];
6 int fa[110];
7 int out[110];
8 int road[110][110];
9 int ans[110][2][110];
10 int val[110];
11 int m,n;
12 void ini()//初始化数据
13 {
14 memset(road,0,sizeof(road));
15 cin>>n>>m;
16 for(int i=1;i<=n;i++)
17 cin>>val[i];
18 for(int i=0;i<n-1;i++)
19 {
20 int a,b;
21 cin>>a>>b;
22 road[a][b]=1;
23 road[b][a]=1;
24 }
25 }
26 void buitr()
27 {
28 queue<int> q;
29 memset(out,0,sizeof(out));
30 memset(vis,0,sizeof(vis));
31 q.push(1);
32 while(!q.empty())
33 {
34 int tem=q.front();
35 //cout<<tem<<' ';
36 q.pop();
37 vis[tem]=1;
38 for(int i=1;i<=n;i++)
39 if(vis[i]!=1&&road[tem][i]==1)
40 {
41 //cout<<i<<' ';
42 q.push(i);
43 out[tem]++;
44 fa[i]=tem;
45 }
46 }
47 // for(int i=1;i<=n;i++)
48 // cout<<fa[i]<<' ';
49 }
50 void work()
51 {
52 memset(ans,0xff,sizeof(ans));//将矩阵置-1
53 for(int j=1;j<=n;j++)
54 {
55 ans[j][1][1]=val[j];
56 ans[j][0][0]=0;
57 }//初始化只选择根节点和什么节点也不选择的
58 int max=0;
59 while(true)
60 {
61 int i;
62 for(i=1;i<=n;i++)
63 if(out[i]==0)
64 break;
65 //cout<<i<<' ';
66 if(i>n)
67 break;
68 for(int k=0;k<2;k++)
69 for(int j=1;j<=m;j++)
70 if(ans[i][k][j]>ans[fa[i]][0][j])
71 ans[fa[i]][0][j]=ans[i][k][j];
72 for(int k=m;k>0;k--)//父节点选择k个的情况
73 if(ans[fa[i]][1][k]>=0)
74 for(int j=1;j<=m-k;j++)
75 {
76 if(ans[i][0][j]>=0&&
77 ans[fa[i]][1][k]+ans[i][0][j]>ans[fa[i]][1][k+j])
78 ans[fa[i]][1][k+j]=ans[fa[i]][1][k]+ans[i][0][j];
79 if(ans[i][1][j]>=0&&
80 ans[fa[i]][1][k]+ans[i][1][j]>ans[fa[i]][1][k+j])
81 ans[fa[i]][1][k+j]=ans[fa[i]][1][k]+ans[i][1][j];
82 }
83 out[i]=-1;
84 out[fa[i]]--;
85 }
86 if(ans[1][0][m]>ans[1][1][m])
87 cout<<ans[1][0][m]<<endl;
88 else
89 cout<<ans[1][1][m]<<endl;
90 }
91 int main()
92 {
93 ini();
94 buitr();
95 work();
96 return 0;
97 }
98
//错误1,忘记更新out[] 导致死循环
//错误2,m有可能比n大
3.测试数据
15 6
1 7 5 6 15 17 3 18 30 4 19 16 1 2 3
1 4
4 3
2 4
5 1
5 7
6 5
7 11
8 7
13 2
15 2
9 3
3 10
6 12
14 6
答案
36