很久没有更新博客了。
今天讲的是《信息学奥赛一本通·初赛真题解析》P351的一道完善程序初赛题(出自2019CSP-S):匠人的自我修养。
该题虽然说是完善程序题,但是对于算法学习比较有价值,涉及拓扑排序,对于初赛通过也有一定帮助,因此这篇文章就产生了。(注:本文部分内容涉及《信息学奥赛一本通·初赛真题解析》的书内容以及答案解析内容。)
题目:
一个匠人决定要学习n个新技术。要想成功学习一个新技术,他不仅要拥有一定的经验值,而且还必须要先学会若干个相关的技术。学会一个新技术之后,他的经验值会增加一个对应的值。给定每个技术的学习条件和习得后获得的经验值,给定他已有的经验值,请问他最多能学会多少个新技术。
输入第一行有两个数,分别为新技术个数n(1≤n≤10^3),以及已有经验值(≤10^7)。
接下来n行,第i行的两个正整数,分别表示学习第i个技术所需的最低经验值(≤10^7),以及学会第i个技术后可获得的经验值(≤10^4)。
接下来n行,第i行的第一个数mi(0≤mi<n),表示第i个技术的相关技术数量,紧跟着mi个两两不同的数,表示第i个技术的相关技术编号。输出最多能学会的新技术个数。
(以下代码时间复杂度为O(n^2),并附注释)
//本题关键在于find函数,类似于拓扑排序,找到一个入度为0并且能够选取的技术,
//然后从DAG上删除,unlock维护每一个技术的度数。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1001;
int n;
int cnt[maxn];//cnti 表示 以i号为前置技术的技术数
int child[maxn][maxn];//childij 表示以i号为前置技术的第j个技术编号
int unlock[maxn];//unlocki 表示每一个技术的前置技术数
int points;//points 存储临时经验值
int threshold[maxn],bonus[maxn];//thresholdi 表示第i号技术的经验门槛 bonusi 表示学习i号技术过后能够获得的经验数
bool find(){
int target = -1;
for(int i=1;i<=n;i++)
if(unlock[i] == 0 && points >= threshold[i]){//此处代表该技术可学。 //条件是前置技术已经全部学完,经验值到达门槛。
target=i;//目标找到,记录
break;//直接结束循环
}
if(target == -1)//如果目标没有找到
return false;//返回false,不能学了
unlock[target] = -1;//如果等于-1,代表已经处理过了。
points += bonus[target];//加上该技能的经验值。
for(int i=0;i<cnt[target];i++)//遍历以新技术作为前置技术的所有技术
unlock[child[target][i]]--;//每一个以已解锁技术为先修技能的技术,入度-1
return true;//学到新技术,返回true
}
int main(){
cin>>n>>points;
for(int i=1;i<=n;i++){
cnt[i]=0;//初始化cnt
cin>>threshold[i]>>bonus[i];//输入
}
for(int i=1;i<=n;i++){
int m;
cin>>m;
unlock[i] = m;//i有m个前置技术
for(int j=0;j<m;j++){
int fa;
cin>>fa;//输入前置技术编号
child[fa][cnt[fa]] = i;//以fa为前置技术的技术中第cnt[fa](以fa为前置技术的技术数)项技术为i(cnt[fa]从0开始)
cnt[fa]++;//前置技术数++
}
}
int ans = 0;
while(find()) ans++;//能够学会一项新技术,ans++
cout<<ans<<endl;//输出结果
return 0;
}
如果不了解拓扑排序,请另找文章先了解该算法,再来看此篇题解。
本篇内容结束,再见。