蓝桥杯 算法训练 强力党逗志芃
题目描述
- 资源限制
时间限制:1.0s 内存限制:256.0MB
- 问题描述
逗志芃励志要成为强力党,所以他将身上所以的技能点都洗掉了重新学技能。现在我们可以了解到,每个技能都有一个前提技能,只有学完了前提技能才能学习当前的技能(有一个最根本的技能不需要前提技能)。学习每个技能要消耗一个技能点,然后可以获得这个技能的威力值。由于逗志芃要陪妹子,所以他希望你教他如何点技能使得威力值最大从而成为强力党。
- 输入格式
第一行两个数n,m表示有n个技能和m个技能点。第二行有n个数,第i个数表示第i个技能的威力值。
之后的n-1行,每行两个数x,y,表示y技能的前提技能是x,也就是说先学第x个技能才能学弟y个技能。
- 输出格式
一个数,最大的威力值。
- 样例输入
3 2
1 10 20
1 2
1 3
- 样例输出
21
- 数据规模和约定
0<n,m<=200, 技能的威力值不超过200。
方案1 暴力递归(过不了,只是提供一个暴力思路)
#include<iostream>
#include<algorithm>
using namespace std;
int n; //n个技能
int m; //m个技能点
int score[200]; //score[i]: 第i+1个技能的威力
bool flag[200]; //已经学习的技能
int fro[200]; //fro[i]=j: i技能的前置技能是j
//函数参数: 已学习的技能数cur
//函数功能:返回能获得的最大威力
int f(int cur){
//没有可用的技能点了
if(cur == m){
return 0;
}
int MAX = 0;
for(int i=0; i<n; ++i){
//如果该技能没有被学习,且该技能的前置技能已经学习
if(!flag[i] && (fro[i]==-1 || flag[fro[i]])){
flag[i] = 1; //学习该技能
MAX = max(MAX, f(cur+1)+score[i]);
flag[i] = 0;
}
}
//返回最大值
return MAX;
}
int main(){
cin>>n>>m;
for(int i=0; i<n; ++i){
cin>>score[i];
fro[i] = -1;
}
for(int i=0; i<n-1; ++i){
int a, b;
cin>>a>>b;
fro[b-1] = a-1;
}
cout<<f(0)<<endl;
return 0;
}
方案1 动态规划,树形dp
#include<iostream>
#include<algorithm>
using namespace std;
int n; //n个技能
int m; //m个技能点
int score[201]; //score[i]: 技能i的威力
bool mp[201][201]; //邻接矩阵, mp[i][j]=1 表示技能j的前置技能是技能i,
//特殊地:如果一个技能没有前置技能,则默认前置技能是0
int ans[201][202]; //ans[i][j]: 以第i个技能为起点,还有j个技能点时所能获得的最大威力
//思路:可以想象为以0技能为起点的一棵树,即不连通的图(邻接矩阵存储边)
//函数参数: 起点技能cur,求出ans[cur][*]的值 (*=0,1,...,m,m+1)
void f(int cur){
//遍历每一个技能点
for(int i=1; i<=n; ++i){
//如果该技能i的前置技能是cur
if(mp[cur][i]){
//1.以i为起点技能继续进行递归
f(i);
//2.根据函数定义:上面函数f结束后,以技能i为起点的ans[i][*]都已经求出
//3.此时更新ans[cur][*]的值
//3.1 由于要保留一个技能点给自己,所以最多还有m-1个技能点可供分配
//3.2 考虑到技能cur下面的子技能(子节点)数未知且有限
// 故必须要从m-1个技能点递减开始考虑分配,保证不会重复相加
//举个例子: 假设1:技能cur下面只有一个子技能i, 且技能威力为3
// 则 ans[i][0]=0, ans[i][1]=3, ans[i][2]=3, ..., ans[i][m]=3
//(1) 如果递增考虑分配:
// 则ans[cur][0]=0, ans[cur][1]=3, ans[cur][2]=ans[cur][1]+ans[i][1]=6
// 此时已经出现错误, 因为cur技能下面只有一个技能i,故当把技能点给i后,
// 即使还有技能点,但也没有技能了,故应该 ans[cur][2]= 0+ans[i][1]=3
//(2) 如果递减考虑分配,则可以避免这个错误
// 因为ans[cur][j]=ans[cur][j-k]+ans[i][k]
// 在本次循环中 ans[cur][j] 要比 ans[cur][j-k] 先更新 (因为j递减)
// 所以更新ans[cur][j]时用的是上一轮循环(指的是i循环)的 ans[cur][j-k]
// 即可以保证把k个技能点分给技能i后,剩余(j-k)个技能点还可以分配给其他技能
// 因为上述(j-k)个技能点在上一轮循环(指的是i循环)中已经分配好了,即完成更新了
for(int j=m-1; j>=0; --j){ //还有j个技能点时
for(int k=0; k<=j; ++k){ //选择是否分给i技能k个技能点(这个递增递减考虑都行)
ans[cur][j] = max(ans[cur][j], ans[cur][j-k]+ans[i][k]);
}
}
}
}
//4.此时cur节点已经完成更新,只需加上技能cur的威力即可
// 即 ans[cur][i] = ans[cur][i-1] + score[cur]; 注意仍需递减更新,理由同上
for(int i=m; i>=0; --i){ //还剩i个技能点时
if(i==0){
ans[cur][0] = 0;
}else{
ans[cur][i] = ans[cur][i-1] + score[cur];
}
}
}
int main(){
cin>>n>>m;
m += 1; //由于人为创造了一个0技能,故需要总技能点数+1
//假设所有技能都没有前置技能,即规定前置技能都是0,即 mp[0][i] = 1
for(int i=1; i<=n; ++i){
mp[0][i] = 1;
}
for(int i=1; i<=n; ++i){
cin>>score[i];
}
for(int i=0; i<n-1; ++i){
int a, b;
cin>>a>>b;
mp[a][b] = 1; //b的前置技能是a
mp[0][b] = 0; //b有前置技能,修改 mp[0][b]的值为0
}
//从0技能点开始,也是人为构造0技能的意义,防止有多棵树存在,即存在森林
f(0);
//输出从0技能点开始有m个技能点时的最大威力
cout<<ans[0][m]<<endl;
return 0;
}