# 天梯赛题解

2022天梯赛题解

​ 其实还是自己的问题,太久没打这么正式的题了,算法能力也下降了太多,虽然在校赛后意识到了,然后开始看y总的课程,但是刷题还是不够,而且时间还太短;自己的读题能力也下降太多了,上次校赛是三个人所以读错题目也会有人纠正,但是这次就没人纠正了,所以在题意理解方面就出了很多偏差。令我感到懊恼的是我们2队就差好像二三十分就可以拿个团体国三,这样子有些学长学姐在保研上就可以加分了,可是我失误了,而且失误的实在是太严重了。

​ 唉,上学期的一些原因导致我不想熬夜,但是这学期的助理工作还有学业让我有没有办法在白天完成工作和学业后还能有多余的时间去学算法、做算法题,也只能调整策略了。

​ 算了算了,意识到了也算是一件好事了,以后晚上回寝室后就看题和刷题吧,如果有cf比赛就和学长学姐一起打cf比赛,没有就学算法吧。

L1-8 静静的推荐

​ 本题末尾为最正确完美解法,前面是自己修正的过程;

​ 最开始的做法:也就是对于每个分数,我们建立二维数组再对其排序,但是这里的排序方式是有问题的,虽然绝大部分样例都能过。

​ 问题在于:对于例如当面时分线是90的时候,[189,1],[189,99]我这种做法就优先选择了[189,99]的人,而把[189,1]放在了第二批推荐名单里了,但实际上,我们可以主动选择[189,1]的人,然后让[189,99]作为题目中描述的“也可以接受”的人;

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5,N2=1e3+5;
struct node{
	int x,y;
}a[N];
int n,k,s;
bool cmp(node cc1,node cc2)
{
	if(cc1.x>cc2.x) return true;
	else if(cc1.x == cc2.x){
		return cc1.y > cc2.y;
	}
	return false;
}
int vis[N];//某个分数是否出现过 
int ex[N];//每个分数上有多少个因为某些要求不符合的人 
int main()
{
	int i,j;
	cin>>n>>k>>s;
	if(k <= 0){
		cout<<0<<"\n";
		return 0;
	}
	int cnt = 0;//低于175 
	for(i = 1;i <= n; i++){
		cin>>a[i].x>>a[i].y;
		if(a[i].x < 175) cnt++;
	}
	
	sort(a+1,a+1+n,cmp);
	
	for(i = 1;i <= n; i++){
		if(vis[a[i].x]){//出现过 
			if(a[i].y < s){//且没有过线 
				ex[a[i].x]++;
			}
		}
		vis[a[i].x] = 1;//标记 
	}
	int ans = n - cnt;
	k--;//本身的那一批 
	for(i = 175; i<=290; i++){
		if(k < ex[i]){ 
			ans -= (ex[i]-k);
		}
		//if(ex[i]) cout<<i<<" "<<ex[i]<<"\n";
	}
	ans = max(ans,0);
	cout<<ans;
}

正解:

#include<bits/stdc++.h>
using namespace std;

const int N =1e6+5;
vector<int> a[N];
set<int>start;//
int ex[N];
int main()
{
	int i,j;
	int n,k,s;cin>>n>>k>>s;
	
	for(i = 1;i <= n; i++){
		int x,y;cin>>x>>y;
		if(x >= 175){
			a[x].push_back(y);
			start.insert(x);
		}
	}
	
	for(auto it =start.begin();it != start.end(); it++){
		sort(a[*it].begin(),a[*it].end());
	}
	k--;
	int ans = 0;
	for(auto it =start.begin();it != start.end(); it++){
		if(!a[*it].empty()){
			ans++;
			a[*it].erase(a[*it].begin());
			if(!a[*it].empty()){
				for(j = a[*it].size()-1;j >= 0; j--){
					if(a[*it][j] < s && ex[*it] < k){
						ans++;
						ex[*it]++;
					}
					else if(ex[*it]<k){
						ans++;
					}
					a[*it].pop_back();
				}
			}
		}
	}
	
	cout<<ans;
	return 0;
}

​ 又去看了看别人的题解,我发现特么的根本就没有那么麻烦啊!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

​ 为什么考试的时候我考虑那么多,还没做出来,我是傻逼。

#include<bits/stdc++.h>
using namespace std;
int n,k,s;
int a[300];
int main()
{
	int i,j;
	cin>>n>>k>>s;
	int ans = 0;
	for(i = 1;i <= n; i++){
		int x,y;
		cin>>x>>y;
		if(x >= 175){
			if(y >= s) ans++;
			else a[x]++;
		}
	}
	for(i = 175;i <= 290; i++){
		ans+=min(k,a[i]);
	}
	cout<<ans<<"\n";
}

L2-1 插松枝

​ 又臭又长,搞人心态的一道题啊,就是考察栈和队列的使用,但是这个体面写得太nm复杂了,我还傻乎乎的去看它,调了半天还没做出来,其实自己读题能力太弱了。

​ 然后补题写了半天也还是有问题;

L2-2 老板的作息表

​ 可能是因为之前准备蓝桥杯的时候在日期问题上经常用三维数组来模拟年月日,所以看到这个题就想用三维数组来模拟时分秒,把对应给出放入时间段置为1,但是写着写着发现这种写法太麻烦了,写了快一百行了。

​ 但是实际上就是一个区间合并的问题。

我们用一个六位数来表示时间点(包括前导0),前两位表示时,中间两位表示分钟,后两位表示秒。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+6;
struct node{
	int s,e;//
}ex[N];
bool cmp(node x,node y)
{//优先按照结束时间递增排序,其次按照开始时间递增排序
	if(x.e < y.e) return true;
	else if(x.e == y.e){
		return x.s<y.s;
	}else{
		return false;
	}
}
void check(int &start)//用于检测数据是否合法,并修改不合法的数据;
{
	if(start%100>=60){
		start-=60;
		start+=100; 
	}
	if(start%10000/100>=60){
		start-=6000;
		start+=10000;
	}
}
int main()
{
	int i,j;
	int n;scanf("%d",&n);
	for(i = 1;i <= n; i++)
	{
		int a,b,c,x,y,z;
		scanf("%d:%d:%d - %d:%d:%d",&a,&b,&c,&x,&y,&z);
		int cc1 = a*10000+b*100+c;
		int cc2 = x*10000+y*100+z;
		ex[i].s = cc1,ex[i].e = cc2;// = {cc1,cc2};
	}
	sort(ex+1,ex+1+n,cmp);
	int start = 0;
	int f = 1;
	for(i = 1;i <= n; i++){
		if(ex[i].s>start){
			int end = ex[i].s;
			check(end);
			if(f == 0) printf("\n");
			printf("%02d:%02d:%02d - %02d:%02d:%02d",start/10000,start%10000/100,
			start%100,end/10000,end%10000/100,end%100);
			start = ex[i].e;
			check(start);
			f = 0;
		}else{
			start = ex[i].e;
			check(start);
		}
	}
	if(start != 235959){
		if(f == 0) printf("\n");
		printf("%02d:%02d:%02d - 23:59:59",start/10000,start%10000/100,
			start%100);
	}
}

​ 看了别人的解法,发现自己写的还是还是太麻烦了;

#include<bits/stdc++.h>
#define PSS pair<string,string> 
using namespace std;

int main()
{
	int i,j;
	int n;scanf("%d",&n);
	vector<PSS >q;
	for(i = 1;i <= n; i++){
		string a,b,c;
		cin>>a>>b>>c;
		q.push_back({a,c});
	}
	q.push_back({"","00:00:00"});
	q.push_back({"23:59:59",""});
	sort(q.begin(),q.end());//pair 默认对first升序,当first相同时对second升序; 
	
	int len = q.size();
	for(i = 0;i < len-1; i++){
		if(q[i].second != q[i+1].first){//因为题目说没有区间重叠的情况,所以不用写"<"
			cout<<q[i].second<<" - "<<q[i+1].first<<"\n";
		} 
	}
	return 0;	
}

L2-3 龙龙送外卖

​ 按照题目的意思是有一棵树,每个结点对应一个之后可能送外卖的地方。然后子节点和父节点的距离全都为1。

​ 我们送一份外卖到一个节点肯定是直接送过去最好,如果有多个点,那最深的点肯定得送到,如果我们先送深的外卖点再去送浅的节点,那势必要返回才行,路径就会变长,因为从深节点到浅节点那一段要走两次,因此我们应当最后走深节点,停留在深节点,这样子路径便最短,**因此我们每次要去维护一个最深结点 **。

​ 对于一个新节点,如果能在之前的路径里“顺便”就把这个新节点的外卖就送了,那当然是最好的,因此我们还需要一个数组来维护某个节点是否已经经过了。

​ 那如果这个新节点是没有经过的,那我们便去找它的父节点,再找父节点的父节点,直到某个节点是在之前的路径经过了的,那多走的距离便是祖节点到新节点的两倍。当然我们还得维护最深结点。

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6+5;
int father[N],depth[N];
bool vis[N];
int idx;//外卖站
int max_len = 0;//这个用来记录我们直接从原点到新增的k节点需要多大距离,然后再考虑是先送这个新增的结点
//再去送之前的结点,还是先送之前的结点再去送新节点。因为要涉及到一个返回的问题 
vector<int> q[N];//要送外卖的地方 ,用二维数组来记录每一条链,记录每个节点的子节点 

void dfs(int fa,int x,int num)//目的是找出每个节点的深度 
{
	depth[x] = num;	
	for(int i = 0;i < q[x].size(); i++){
		int v= q[x][i];	
		if(v == fa) continue;//跳过自己 
		dfs(x,v,num+1);
	}
}
int dfs_add(int fa,int len)
{
	if(vis[fa]) return 2*len;
	vis[fa] = true;
	return dfs_add(father[fa],len+1);	
}
 
int main()
{
	int i,j;
	int n,m;cin>>n>>m;
	for(i = 1;i <= n; i++){
		int x;cin>>x;
		if(x == -1) idx = i,vis[i] = true;
		else{ 
			father[i] = x;
			q[x].push_back(i);
		}
	}
	
	dfs(-1,idx,0);
	
	int sum = 0;//增加的路程 
	while(m--)
	{
		int k;cin>>k;//又多出来一个节点 
		sum += dfs_add(k,0); //我们在dfs_add里面标记某个点是否走过的目的就是如果之后新加入的点其实在之前的路径是经过了的话那其实不用多走,顺路就送了就行;而如果该点是没有到过的,那么我们便应该去找他父节点、爷节点……是否有在之前经过的路径上,假如祖上某个节点x是在之前的路径上,那么我们从x节点额外去送到新增节点后再返回,因此要加上多出来的这段距离,;但是如果这个新增节点的深度比我们之前到过的最深节点还深,那么我们应该是要先把别的点送了后再考虑送这个新增节点,因此在下面,我们要减去新省下来的距离。
		max_len = max(max_len,depth[k]);//
        //我们是想要最后一份儿外卖是送到节点最深的点,这样子省去的路径就是最多的,因此我们要维护一个最深路径
        
		cout<<sum - max_len<<endl;
	}
	
}

L2-4 大众情人

​ 考察的知识点是弗洛伊德算法;

(8条消息) 弗洛伊德(Floyd)算法c++版_我叫大睿智的博客-CSDN博客_floyd算法c++

​ 因为题目中“从小蓝的眼中看去,他和小红之间的距离为 1,只差一层窗户纸;但在小红的眼里,她和小蓝之间的距离为 108000”,即说明a[x] [y] != a[y] [x],不具备传递性

#include<bits/stdc++.h>
using namespace std;
const int N = 505;

int a[N][N];
int dis[N];//每个人对应的最远的那个
int sex[N];//性别,0女性,1男性 
int main()
{
	int i,j,k;
	int n;cin>>n;
	for(i = 1;i <= n; i++){
		for(j = 1;j <= n; j++){
			a[i][j] = (i==j)?0:1e9;
		}
	}
	for(i = 1;i <= n; i++){
		char op[2];
		cin>>op>>k;
		if(*op=='M') sex[i] = 1;
		for(j = 1;j <= k; j++){
			int x,y;
			scanf("%d:%d",&x,&y);
			a[i][x] = y;// 
			//这里可以不用s[x][i] = s[i][x];,不具备传递性
		}
	} 
	//弗洛伊德 
	for(k = 1;k <= n; k++){//中转站 
		for(i = 1;i <= n; i++){
			for(j = 1;j <= n; j++){
				//不需要判断是否自己到自己,因为肯定是0
				a[i][j] = min(a[i][j],a[i][k]+a[k][j]);	 
			}
		}
	}
	
	for(i = 1;i <= n; i++){
		dis[i] = 0;
		for(j = 1;j <= n; j++){
			if(sex[i] != sex[j]){
				dis[i] = max(dis[i],a[j][i]);
				//因为异性缘是对i最无感的那个人决定的,所以是j->i,不是i->j; 
			}
		}
	}
	
	for(k = 0;k <= 1; k++){//先女性,后男性 
		int d_min = 2e9;
		for(i = 1;i <= n; i++){
			if(sex[i] == k){
				d_min = min(d_min,dis[i]);
			}
		}
		int f = 0;//第一个?
		for(j = 1;j <= n; j++){
			if(sex[j]==k&&dis[j] == d_min){
				if(f != 0) cout<<' ';
				cout<<j;
				f++;
			}
		}puts("");
	}
	
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值