训练计划通俗题解(2个方法)(CSP-2022-12-2)

问题描述

为了在大赛中取得好成绩,顿顿准备在 n 天时间内完成“短跑”、“高中物理”以及“核裂变技术”等总共 m 项科目的加强训练。其中第 i 项(1≤i≤m)科目编号为 i,也可简称为科目 i。已知科目 i 耗时 ti 天,即如果从第 a 天开始训练科目 i,那么第 a+ti−1 天就是该项训练的最后一天。

大部分科目的训练可以同时进行,即顿顿在同一天内可以同时进行多项科目的训练,但部分科目之间也存在着依赖关系。如果科目 i 依赖科目 j,那么只能在后者训练结束后,科目 i 才能开始训练。具体来说,如果科目 j 从第 a 天训练到第 a+tj−1 天,那么科目 i 最早只能从第 a+tj 天开始训练。还好,顿顿需要训练的 m 项科目依赖关系并不复杂,每项科目最多只依赖一项别的科目,且满足依赖科目的编号小于自己。那些没有任何依赖的科目,则可以从第 1 天就开始训练。

对于每一项科目,试计算:

1)最早开始时间:该科目最早可以于哪一天开始训练?

2)最晚开始时间:在不耽误参赛的前提下(n 天内完成所有训练),该科目最晚可以从哪一天开始训练?

n 天内完成所有训练,即每一项科目训练的最后一天都要满足 ≤n。需要注意,顿顿如果不能在 n 天内完成全部 m 项科目的训练,就无法参加大赛。这种情况下也就不需要再计算“最晚开始时间”了。

输入格式

从标准输入读入数据。

输入共三行。

输入的第一行包含空格分隔的两个正整数 n 和 m,分别表示距离大赛开幕的天数和训练科目的数量。

输入的第二行包含空格分隔的 m 个整数,其中第 i 个(1≤i≤m)整数 pi 表示科目 i 依赖的科目编号,满足 0≤pi<i;pi=0 表示科目 i 无依赖。

输入的第三行包含空格分隔的 m 个正整数,其中第 i 个(1≤i≤m)数 ti 表示训练科目 i 所需天数,满足 1≤ti≤n。

输出格式

输出到标准输出中。

输出共一行或两行。

输出的第一行包含空格分隔的 m 个正整数,依次表示每项科目的最早开始时间。

如果顿顿可以在 n 天内完成全部 m 项科目的训练,则继续输出第二行,否则输出到此为止。

输出的第二行包含空格分隔的 m 个正整数,依次表示每项科目的最晚开始时间。

样例 1 输入

10 5
0 0 0 0 0
1 2 3 2 10

样例 1 输出

1 1 1 1 1
10 9 8 9 1

样例 2 输入

10 7
0 1 0 3 2 3 0
2 1 6 3 10 4 3

样例 2 输出

1 3 1 7 4 7 1

样例 3 输入

10 5
0 1 2 3 4
10 10 10 10 10

样例 3 输出

1 11 21 31 41

子任务

70% 的测试数据满足:顿顿无法在 n 天内完成全部 m 项科目的训练,此时仅需输出一行“最早开始时间”;

全部的测试数据满足 0<n≤365 且 0<m≤100。


题解:

        第一次看到这道题,说实话我联想到建立一个树,树有一个虚的根节点,所有没有依赖科目的科目都连接它,剩下的有依赖科目的科目都作为它所依赖科目的子节点,建树的示意图如下:

这样求最早开始时间和最晚开始时间在递归遍历树的过程中顺带就实现了。

具体而言,我定义两个递归遍历树的函数,is_superior()和late()。

具体代码如下:

AC Code

#include<iostream>
using namespace std;
int n,m,num,flag=false;
struct tree{
	int total=0;
	int time;
	int son[101];
}tree[101];
int ans[101];
int max(int a,int b){
	return a>b?a:b;
}
int is_superior(int leijia,int node,int father){
	if(node){    //如果不是根节点 
		if(!father){      //如果它的父节点是根节点 
			ans[node]=1;     
			leijia=1+tree[node].time;      //leijia是给该节点的子节点用的,它准确来说是子节点在leijian天开始 
		}
		else{            
			ans[node]=leijia;
			leijia+=tree[node].time;
		}
	} 
	if(ans[node]+tree[node].time>n){
		flag=true;
	}
	for(int i=1;i<=tree[node].total;i++){
		is_superior(leijia,tree[node].son[i],node);
	}
}
int late(int node,int father){
	int maxi=0;
	if(tree[node].total==0){
		ans[node]=tree[node].time;
		return tree[node].time;
	}
	for(int i=1;i<=tree[node].total;i++){	
		maxi=max(maxi,late(tree[node].son[i],node));
	} 
	ans[node]=tree[node].time+maxi;
	return tree[node].time+maxi;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>num;
		tree[num].son[++tree[num].total]=i;
	}
	for(int i=1;i<=m;i++){
		cin>>num;
		tree[i].time=num;
	}
    is_superior(0,0,0);
    for(int i=1;i<=m;i++){
    	printf("%d ",ans[i]);
	}
	if(!flag){
		putchar('\n');
		late(0,0);
		for(int i=1;i<=m;i++){
    	printf("%d ",n-ans[i]+1);
	    }
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值