CSP-S 2021 廊桥分配

2021年的提高组第一题

学校模拟测试的时候居然想到了AC的代码(((bushi

luogu P7913

题目意思

  大体意思就是有n个廊桥,m1起国内航班,m2起国际航班,国内区和国际区是分开的,把n个廊桥分到两个区,飞机想要尽可能的停在廊桥处,而不停在远机处,每架飞机都有各自的起降时间,廊桥按到达顺序供给飞机,问怎么分廊桥可使得停在廊桥的飞机最多?

  数据范围:1n105,m1,m2≥1,m1+m2≤105,ai<bi≤108

思路

 因为题目中说廊桥是按飞机抵达的先后顺序分配的,我先想到了一个暴力思路:就是枚举分给国内的廊桥的个数(则分给国外的廊桥个数为n-i),再去枚举国内国外能有多少架飞机可以停在廊桥,取和的最大值,这个思路的复杂度在不优化的情况下大概是O(n^2(m1+m2)),这个复杂度一眼就知道会炸,大概只能过前20%的数据

  接着,我否定了我上一个暴力思路后又想出了更好的一个暴力思路(别问我为啥老想暴力思路,因为正解也不是那么容易能思考出来的,这个离着我的AC思路已经很近了):我们不如先假设廊桥有无数个,去遍历国内(国外)的航班,设一个sum[i]表示第i个廊桥会停几架飞机,如果此时有空机位,飞机就停在空机位,sum[i]++,否则廊桥数tot++,sum[tot]=1,最后再用一个前缀和p,就可以求出当有i个廊桥的时候,可以停放多少飞机。

  这个思路的时间复杂度是O(n*(m1+m2)),完全可以过掉前40%的点了。但是我的目标是正解,我们发现,在找空机位的时候特别浪费时间,是O(n)的复杂度,我们应该去考虑一种log级别的方法。

  我先考虑的就是优先队列q1(太香了),把廊桥的编号和飞机起飞时间压入队列,按起飞时间由小到大排序,如果队首元素起飞时间大于当前遍历到的飞机,那么直接新建廊桥,否则将其停在队首廊桥上,压入队列。

 注意!这里我犯了个非常致命的错误!!!

  如果只是判定是否需要新建廊桥,是没有问题的,但是如果直接停在队首廊桥就会出现一个错误——可能会有编号更小的廊桥可以停,这样就会导致:假设这个飞机是可以停在1号廊桥,但由于3号廊桥的飞机起飞更早,导致该飞机停在了3号廊桥(所以会有啥后果呢?),那么我们在存储sum[i]的时候,就会把该飞机编入3号廊桥,也就是说至少要给国内分配3个廊桥时该飞机才可以停,那这显然不行。

正解

  于是,我又增加了一个优先队列q2,用来从小到大存当前空机位廊桥的编号,如果有飞机的降落时间大于q1队首的起飞时间,那么我们直接把队首元素弹出,并把所对的廊桥编号压入q2,然后取q2队首编号的廊桥作为当前飞机的停机点。如果飞机的降落时间小于q1队首的起飞时间,先看看q2是否有空机位,如果没有再创建一个廊桥。时间复杂度是O((m1+m2)logn),可以过。

代码

  考试时写的代码,难看繁琐,勿喷

#include <bits/stdc++.h>
using namespace std;
int n,m1,m2;

struct qu{
	int l;
	int r;
};
qu s1[101000],s2[101000];

struct node{
	int id;
	int ti;
	bool operator < (const node &x) const{
		return ti>x.ti;
	}
};

priority_queue<node> q;
priority_queue<int,vector<int>,greater<int> > q2; 
int sum1[101000],sum2[101000],p1[101000],p2[101000];
int tot1,tot2;

bool cmp(qu x,qu y){
	return x.l<y.l;
}
int main (){
	cin >> n >> m1 >> m2;
	for(int i=1;i<=m1;i++)
		cin >> s1[i].l >> s1[i].r;
	for(int i=1;i<=m2;i++)
		cin >> s2[i].l >> s2[i].r;
		
	sort(s1+1,s1+m1+1,cmp);//先按降落时间从小到大排序 
	sort(s2+1,s2+m2+1,cmp);
	
	for(int i=1;i<=m1;i++){
		if(q.empty()||q.top().ti>s1[i].l){//如果当前所用着的廊桥已经没有可以停下的了 
			if(!q2.empty()) {//如果有空机位先停空机位 
				sum1[q2.top()]++;
				q.push({q2.top(),s1[i].r});
				q2.pop();
				continue;
			}
			sum1[++tot1]++;//否则新建廊桥 
			q.push({tot1,s1[i].r});
		}else{
			while(!q.empty()&&q.top().ti<=s1[i].l){//先把所有符合要求的廊桥出队 
				node u=q.top();
				q2.push(u.id);//压入q2 
				q.pop();
			}
			sum1[q2.top()]++;//sum存储 
			q.push({q2.top(),s1[i].r});
			q2.pop();
		}
	}
	
	while(!q.empty()) q.pop();//清空q1 q2 
	while(!q2.empty()) q2.pop();
	
	for(int i=1;i<=n;i++) p1[i]=p1[i-1]+sum1[i];//前缀和求p,表示i个廊桥可停几个飞机 
	
	for(int i=1;i<=m2;i++){//国外同理 
		if(q.empty()||q.top().ti>s2[i].l){
			if(!q2.empty()) {
				sum2[q2.top()]++;
				q.push({q2.top(),s2[i].r});
				q2.pop();
				continue;
			}
			sum2[++tot2]++;
			q.push({tot2,s2[i].r});
		}else{
			while(!q.empty()&&q.top().ti<=s2[i].l){
				node u=q.top();
				q2.push(u.id);
				q.pop();
			}
			sum2[q2.top()]++;
			q.push({q2.top(),s2[i].r});
			q2.pop();
		}
	}
	
	for(int i=1;i<=n;i++) p2[i]=p2[i-1]+sum2[i];
	
	int ans=0;
	for(int i=0;i<=n;i++){//枚举廊桥的分配个数 
		ans=max(ans,p1[i]+p2[n-i]); //取最大值 
	}
	cout << ans;
	
	return 0;
}

总结

  总体来说第一题还是有一定的思维的,这是本蒟蒻第一次写题解,若有什么问题请指出

下课!

posted @   蒟蒻的咸鱼  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示