CSP 提高级 2021 第一题 廊桥分配 airport 20分(纯暴力)+40分(堆优化)+100分(虚拟廊桥)

总目录详见:NOIP 提高组 复赛 试题 目录 信奥 历年

在线测评地址:洛谷 P7913 [CSP-S 2021] 廊桥分配

1.20分(纯暴力)

对于 20% 的数据,n≤100,m1+m2≤100

所有a1,i​,b1,i​,a2,i​,b2,i​ 为数值不超过 10^8 的互不相同的正整数,且保证对于每个 i∈[1,m1],都有 a1,i​<b1,i​,以及对于每个 i∈[1,m2​],都有 a2,i​<b2,i​

关键字:先到先得。若国际区有空余,有国际航班到站,无论该航班停靠多长时间,必须给予停靠。

数据量不大,n占用第一层循环,m1,m2均占用第二层循环,程序的最多计算次数100*100=10000,完全不会超时。

因飞机有抵达和飞离时间,需要动用结构体。

现给定未来一段时间飞机的抵达、离开时刻,没有说明按什么顺序抵达,稳妥起见,还是要进行抵达时间的排序。

有一个疑问,抵达飞机和飞离飞机,能同时发生吗?(整个题目看完,才发现,这不可能,题中说:所有a1,i​,b1,i​,a2,i​,b2,i​ 为数值不超过 10^8 的互不相同的正整数)

评估算法的时间复杂度O(n*(m1+m2)*n*log(n)),即100*100*100*2=2*10^6,不会超时。

20分代码如下:

#include <bits/stdc++.h>
#define maxn 110
using namespace std;
struct node{
	int a,b;
}gn[maxn],gj[maxn],gn2[maxn],gj2[maxn];
bool cmp(node a,node b){
	return a.a<b.a;//按抵达时间,自小到大排序 
}
bool cmp2(node a,node b){
	return a.b>b.b;//按飞离时间,自大到小排序 
}
int main(){
	int n,m1,m2,n1,n2,i,j,cnt1=0,cnt2=0,cnt=0,mx=0;
	scanf("%d%d%d",&n,&m1,&m2);
	for(i=1;i<=m1;i++)
		scanf("%d%d",&gn[i].a,&gn[i].b);
	sort(gn+1,gn+1+m1,cmp);
	for(i=1;i<=m2;i++)
		scanf("%d%d",&gj[i].a,&gj[i].b);
	sort(gj+1,gj+1+m2,cmp);
	for(n1=0;n1<=n;n1++){
		n2=n-n1;
		cnt=0,cnt1=0,cnt2=0;
		if(n1){
			for(i=1;i<=m1;i++){//m1,m2是对称的代码 
				if(cnt1<n1){//还有空余 
					cnt1++,cnt++;
					gn2[cnt1]=gn[i];
				}
				else{//停满 
					sort(gn2+1,gn2+1+cnt1,cmp2);
					while(cnt1&&gn2[cnt1].b<gn[i].a)cnt1--;
					if(cnt1<n1){//有空闲,继续加入 
						cnt1++,cnt++;
						gn2[cnt1]=gn[i];
					}
				}
			}
		}
		if(n2){
			for(i=1;i<=m2;i++){//m1,m2是对称的代码 
				if(cnt2<n2){
					cnt2++,cnt++;
					gj2[cnt2]=gj[i];
				}
				else{
					sort(gj2+1,gj2+1+cnt2,cmp2);
					while(cnt2&&gj2[cnt2].b<gj[i].a)cnt2--;
					if(cnt2<n2){//有空闲,继续加入 
						cnt2++,cnt++;
						gj2[cnt2]=gj[i];
					}
				}
			}
		}
		mx=max(mx,cnt);
	}
	printf("%d\n",mx);
	return 0;
} 

极限能到25分

 

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
struct node{
	int a,b;
}gn[maxn],gj[maxn],gn2[maxn],gj2[maxn];
bool cmp(node a,node b){
	return a.a<b.a;//按抵达时间,自小到大排序 
}
bool cmp2(node a,node b){
	return a.b>b.b;//按飞离时间,自大到小排序 
}
int main(){
	int n,m1,m2,n1,n2,i,j,cnt1=0,cnt2=0,cnt=0,mx=0;
	scanf("%d%d%d",&n,&m1,&m2);
	for(i=1;i<=m1;i++)
		scanf("%d%d",&gn[i].a,&gn[i].b);
	sort(gn+1,gn+1+m1,cmp);
	for(i=1;i<=m2;i++)
		scanf("%d%d",&gj[i].a,&gj[i].b);
	sort(gj+1,gj+1+m2,cmp);
	for(n1=0;n1<=n;n1++){
		n2=n-n1;
		cnt=0,cnt1=0,cnt2=0;
		if(n1){
			for(i=1;i<=m1;i++){//m1,m2是对称的代码 
				if(cnt1<n1){//还有空余 
					cnt1++,cnt++;
					gn2[cnt1]=gn[i];
				}
				else{//停满 
					sort(gn2+1,gn2+1+cnt1,cmp2);
					while(cnt1&&gn2[cnt1].b<gn[i].a)cnt1--;
					if(cnt1<n1){//有空闲,继续加入 
						cnt1++,cnt++;
						gn2[cnt1]=gn[i];
					}
				}
			}
		}
		if(n2){
			for(i=1;i<=m2;i++){//m1,m2是对称的代码 
				if(cnt2<n2){
					cnt2++,cnt++;
					gj2[cnt2]=gj[i];
				}
				else{
					sort(gj2+1,gj2+1+cnt2,cmp2);
					while(cnt2&&gj2[cnt2].b<gj[i].a)cnt2--;
					if(cnt2<n2){//有空闲,继续加入 
						cnt2++,cnt++;
						gj2[cnt2]=gj[i];
					}
				}
			}
		}
		mx=max(mx,cnt);
	}
	printf("%d\n",mx);
	return 0;
} 

 还是有惊喜的,多得了5分。

2.40分

对于 40%的数据,n≤5000,m1​+m2​≤5000

所有a1,i​,b1,i​,a2,i​,b2,i​ 为数值不超过 10^8 的互不相同的正整数,且保证对于每个 i∈[1,m1],都有 a1,i​<b1,i​,以及对于每个 i∈[1,m2​],都有 a2,i​<b2,i​

对纯暴力进行优化,用优先队列存储停放在廊桥的飞机,评估算法的时间复杂度O(n*(m1+m2)*log(n)),即5000*5000*log(5000)=2.5*10^7*4=10^8,大概率超时,不过可以试一试。

40分代码如下:

 

#include <bits/stdc++.h>
#define maxn 5010
using namespace std;
struct node{
	int a,b;
}gn[maxn],gj[maxn];
struct node2{
	int a,b;
	friend bool operator < (const node2 &nd1,const node2 &nd2){
		return nd1.b>nd2.b;//按飞离时间,自小到大排序
	}
}hb;
bool cmp(node a,node b){
	return a.a<b.a;//按抵达时间,自小到大排序 
}
int main(){
	int n,m1,m2,n1,n2,i,j,cnt1=0,cnt2=0,cnt=0,mx=0,a,b;
	scanf("%d%d%d",&n,&m1,&m2);
	for(i=1;i<=m1;i++)
		scanf("%d%d",&gn[i].a,&gn[i].b);
	sort(gn+1,gn+1+m1,cmp);
	for(i=1;i<=m2;i++)
		scanf("%d%d",&gj[i].a,&gj[i].b);
	sort(gj+1,gj+1+m2,cmp);
	for(n1=0;n1<=n;n1++){
		n2=n-n1;
		cnt=0;
		priority_queue<node2> gn2,gj2;
		if(n1){
			for(i=1;i<=m1;i++){//m1,m2是对称的代码
				if(gn2.empty()!=true){//尝试腾出廊桥 
					b=gn2.top().b;
					if(b<gn[i].a)
						gn2.pop();
				} 
				if(gn2.size()<n1){//还有空余 
					cnt++;
					hb.a=gn[i].a;
					hb.b=gn[i].b;
					gn2.push(hb);//用堆减少计算次数 
				}
			}
		}
		if(n2){
			for(i=1;i<=m2;i++){//m1,m2是对称的代码 
				if(gj2.empty()!=true){//尝试腾出廊桥 
					b=gj2.top().b;
					if(b<gj[i].a)
						gj2.pop();
				} 
				if(gj2.size()<n2){//还有空余 
					cnt++;
					hb.a=gj[i].a;
					hb.b=gj[i].b;
					gj2.push(hb);//用堆减少计算次数
				}
			}
			
		}
		mx=max(mx,cnt);
	}
	printf("%d\n",mx);
	return 0;
} 

 极限能到45分,满意

 

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
struct node{
	int a,b;
}gn[maxn],gj[maxn];
struct node2{
	int a,b;
	friend bool operator < (const node2 &nd1,const node2 &nd2){
		return nd1.b>nd2.b;//按飞离时间,自小到大排序
	}
}hb;
bool cmp(node a,node b){
	return a.a<b.a;//按抵达时间,自小到大排序 
}
int main(){
	int n,m1,m2,n1,n2,i,j,cnt1=0,cnt2=0,cnt=0,mx=0,a,b;
	scanf("%d%d%d",&n,&m1,&m2);
	for(i=1;i<=m1;i++)
		scanf("%d%d",&gn[i].a,&gn[i].b);
	sort(gn+1,gn+1+m1,cmp);
	for(i=1;i<=m2;i++)
		scanf("%d%d",&gj[i].a,&gj[i].b);
	sort(gj+1,gj+1+m2,cmp);
	for(n1=0;n1<=n;n1++){
		n2=n-n1;
		cnt=0;
		priority_queue<node2> gn2,gj2;
		if(n1){
			for(i=1;i<=m1;i++){//m1,m2是对称的代码
				if(gn2.empty()!=true){//尝试腾出廊桥 
					b=gn2.top().b;
					if(b<gn[i].a)
						gn2.pop();
				} 
				if(gn2.size()<n1){//还有空余 
					cnt++;
					hb.a=gn[i].a;
					hb.b=gn[i].b;
					gn2.push(hb);//用堆减少计算次数 
				}
			}
		}
		if(n2){
			for(i=1;i<=m2;i++){//m1,m2是对称的代码 
				if(gj2.empty()!=true){//尝试腾出廊桥 
					b=gj2.top().b;
					if(b<gj[i].a)
						gj2.pop();
				} 
				if(gj2.size()<n2){//还有空余 
					cnt++;
					hb.a=gj[i].a;
					hb.b=gj[i].b;
					gj2.push(hb);//用堆减少计算次数
				}
			}
			
		}
		mx=max(mx,cnt);
	}
	printf("%d\n",mx);
	return 0;
} 

3.100分

对于 100%的数据,1≤n≤100000,1≤m1​+m2​≤100000.

所有a1,i​,b1,i​,a2,i​,b2,i​ 为数值不超过 10^8 的互不相同的正整数,且保证对于每个 i∈[1,m1],都有 a1,i​<b1,i​,以及对于每个 i∈[1,m2​],都有 a2,i​<b2,i​

猜测算法的时间复杂度是O(n*(logm1+logm2))

让我们先忽略廊桥数量的限制来安排航班。我们维护一个空闲的廊桥队列,每到达一架航班,就给它安排编号最小的廊桥供其使用。

现在加上廊桥数量的限制。容易发现刚才的廊桥分配方法直接就帮我们解决了廊桥限制的问题:如果当前有 n 个廊桥可供使用,则分配到 n+1 号及以后的廊桥实质上就是分配到远机位了,不需要再做任何额外的处理。

到这里做法就很清晰了:我们按照开头提到的分配方法来安排航班的停靠位置,记录各廊桥停靠的航班数,做一个前缀和,最后枚举分配给某个区的廊桥数,算出各情况下两区实际使用廊桥的航班数总和,即可解决本题。

#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
typedef pair<int, int> pii;
struct range {
  int x, y;
} a[100005], b[100005];
int res1[100005], res2[100005];
int n;
bool cmp(const range& a, const range& b) { return a.x < b.x; }
void calc(range* t, int m, int* res) {
  priority_queue<pii, vector<pii>, greater<pii> > lq; // 等待离港航班队列
  priority_queue<int, vector<int>, greater<int> > wq; // 空闲廊桥队列
  for (int i = 1; i <= n; i++) wq.push(i);
  for (int i = 1; i <= m; i++) {
    while (!lq.empty() && t[i].x >= lq.top().first) {
      wq.push(lq.top().second);
      lq.pop();
    }
    if (wq.empty()) continue;
    int dest = wq.top();
    wq.pop();
    res[dest]++;
    lq.push(make_pair(t[i].y, dest));
  }
  for (int i = 1; i <= n; i++) res[i] += res[i - 1];
}
int main() {
  int m1, m2;
  cin >> n >> m1 >> m2;
  for (int i = 1; i <= m1; i++) cin >> a[i].x >> a[i].y;
  for (int i = 1; i <= m2; i++) cin >> b[i].x >> b[i].y;
  sort(a + 1, a + m1 + 1, cmp);
  sort(b + 1, b + m2 + 1, cmp);
  calc(a, m1, res1);
  calc(b, m2, res2);
  int ans = 0;
  for (int i = 0; i <= n; i++) {
    ans = max(ans, res1[i] + res2[n - i]);
  }
  cout << ans << endl;
  return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值