【CSP-S 2021】廊桥分配 题解(多种做法)

嗯。。

这道题是对我而言意义非凡的一道题

我第一次打CSP-S见到的第一道题,考场上20pts。从2021年10.24CSP当天开始 在洛谷交了很多遍。

之后看题解,有很多种这个题的做法,于是学习了一下。思想都大同小异,在实现上有着区别。、

简单来说,就是STL全家桶。

(PS:这几种做法是笔者在不同时间写的不同做法,时间跨度很大,代码风格很大,请见谅)

简易题面

分为两种停机坪,国内和国外,有 m 1 + m 2 m1 + m2 m1+m2个,给你 [ l , r ] [l,r] [l,r],为落地时间和起飞时间,不能降落的就等会再降落或者安排另外一个停机坪(如果有的话)。

给定 n , m 1 , m 2 n , m1 , m2 n,m1,m2和起飞落地时间,求最多能容纳多少飞机。

40pts 做法

我们考虑一下,

这个起飞时间其实是一个线性关系,也就是在一条时间线上,你总能找到个飞机的起飞时间和落地时间,

不过有了 n n n个停机坪的限制,我们就要考虑一些其他的东西。

设最后的答案为 t o t tot tot,那么每当一个飞机落地的时候,那么 t o t + + tot++ tot++,飞机起飞的时候, t o t − − tot-- tot

※如果 t o t tot tot + + ++ ++操作之后超过停机坪的限制数量,那么它就不能降落,同时进行标记,以防后面操作时进行 − 1 −1 1 操作。

具体做法:

(1)读入航班信息按照抵达时刻从小到大进行排序并对时刻进行离散化。

(2)枚举每个廊桥,并在上述的※条件下下对每个离散化后的时刻进行处理,依次得到最大航班数量。

(3)进行统计。

它的复杂度? O ( n m 1 + n m 2 ) O(nm_1 + nm_2) O(nm1+nm2),因为题面给的 m 1 + m 2 < = 5000 m1+m2<=5000 m1+m2<=5000,那其实在极端数据下近似为 O ( n 2 ) O(n^2) O(n2)的。

#include
using namespace std;
int n ;
int m1 , m2;
struct node{
	int l , r;
	bool operator<(const node &x)const{
        return l < x.l;
    }
}a[1000001] , b[1000001];
int len_a , len_b;
int Discrete_a[10000001];
int Discrete_b[10000001];
int pos_a[100000][2];// 0 航班序号、1 存储该时刻是否是飞机抵达
int pos_b[100000][2];

bool vis[1000000];
int ans ;

int solve(int pos[][2] , int len , int x){
	int cnt_Bridge = 0 , Can_in = 0;  //廊桥数量  可以进入的数量
	memset(vis , 0 , sizeof(vis));
	for(int i = 1 ; i <= len ; i ++){
		if(pos[i][1]){    
			if(cnt_Bridge < x){ //约束条件
				++cnt_Bridge;
				++Can_in;
				vis[pos[i][0]] = 1 ;
			}
		}else{
			if(vis[pos[i][0]])
				--cnt_Bridge;
		}
	}
	return Can_in;
} 


int main(){
	cin >> n >> m1 >> m2;
	for(int i = 1 ; i <= m1 ; i ++){
		cin >> a[i].l >> a[i].r;
		Discrete_a[++len_a] = a[i].l;
		Discrete_a[++len_a] = a[i].r;
	}
	for(int i = 1 ; i <= m2 ; i ++){
		cin >> b[i].l >> b[i].r;
		Discrete_b[++len_b] = b[i].l;
		Discrete_b[++len_b] = b[i].r;
	}
	sort(a + 1 , a + 1 + m1 );
	sort(b + 1 , b + 1 + m2 );
	sort(Discrete_a + 1 , Discrete_a + 1 + m1 * 2);
	sort(Discrete_b + 1 , Discrete_b + 1 + m2 * 2);
	
    // 全是排序
    
	for(int i = 1 ; i <= m1 ; i ++){
		a[i].l = lower_bound(Discrete_a + 1 , Discrete_a + len_a + 1 , a[i].l) - Discrete_a;
		a[i].r = lower_bound(Discrete_a + 1 , Discrete_a + len_a + 1 , a[i].r) - Discrete_a;
		pos_a[a[i].l][0] = pos_a[a[i].r][0] = i;
		pos_a[a[i].l][1] = 1;
	}
	for(int i = 1 ; i <= m2 ; i ++){
		b[i].l = lower_bound(Discrete_b + 1 , Discrete_b + len_b + 1 , b[i].l) - Discrete_b;
		b[i].r = lower_bound(Discrete_b + 1 , Discrete_b + len_b + 1 , b[i].r) - Discrete_b;
		pos_b[b[i].l][0] = pos_b[b[i].r][0] = i;
		pos_b[b[i].l][1] = 1;
	}
	
    //全是离散化
	for(int i = 0 ; i <= n ; i ++)
		ans = max(ans , solve(pos_a , len_a , i) + solve(pos_b , len_b , n - i));
	//最后的答案就是sol(posa,lena,i)+sol(posb,lenb,n-i)的最大值,我们下一步的优化从优化它开始。
	cout << ans << endl; 
	return 0;
} 

100pts做法1 set

因为所有的飞机的起落都没有交集(这里指的是一条线段上,两个飞机在时间相交的情况不可能停在一个飞机坪上),所以航班其实可以表达成一个集合。

想要快速处理将飞机停靠在不同数量的停机坪上所容纳的飞机,就不要去重复处理同一件事。

这一点,可以用 前缀和 解决。

那我们就需要一个能够在 l o g n logn logn 内查找飞机的最晚离开时间的算法。

这一点,set最好不过。

#include 
#define x first
#define y second
#define For(i, j, k) for (int i = j; i <= k; i ++ )
using namespace std;
const int N = 100010, INF = N;
typedef pair<int, int> PII;
int ans;
int n, m1, m2;
PII a[N];
PII b[N];

set<PII> A, B;
set<PII>::iterator id;
int num1[N], num2[N];
int add(set<PII> &S){
    int res = 0, now = 0;
    For(i,0,INF){
        id = S.lower_bound({now, now}); // 寻找进入机场的飞机中最早的一个, 返回它所在的迭代器
        if (id == S.end()) break; 
        res ++;
        now = (*id).y; 
        S.erase(id);
    }
    return res;
}
int main()
{
    scanf("%d %d %d", &n, &m1, &m2);
    For(i, 1, m1) scanf("%d %d", &a[i].x, &a[i].y);
    For(i, 1, m2) scanf("%d %d", &b[i].x, &b[i].y);
    For(i, 1, m1) A.insert(a[i]);
    For(i, 1, m2) B.insert(b[i]);
    For(i, 1, n) num1[i] = num1[i - 1] + add(A); 
    For(i, 1, n) num2[i] = num2[i - 1] + add(B);
    For(i, 0, n) ans = max(ans, num1[i] + num2[n - i]);

    printf("%d", ans);
    return 0;
}

100pts做法2 优先队列

很显然吧?

维护两个优先队列,一个是等待离开的航班队列,一个是空闲的廊桥队列

如果当前有 i i i个停机坪可以放飞机,那么我们就把放不了飞机放在 i + 1 i+1 i+1处,这样的话可以构建出一个队列。

记录各停机坪停靠的航班数,同样的前缀和,然后枚举廊桥,然后再把两区的航班数加起来。


#include
using namespace std;
struct Range {
	int x , y;
} a[1000001] , b[1000001];
int res1[10000001];
int res2[10000001];
int n ;
bool mycmp(const Range& a ,const Range& b){
	return a.x < b.x;
}
void Work(Range* t , int m , int *res) {
	priority_queue<pair<int , int> , vector<pair<int , int>> , greater<pair<int ,int >> > wait;
	priority_queue<int , vector<int> , greater<int> > leisure;
	for(int i = 1 ; i <= n ; i++) leisure.push(i);
	for(int i = 1 ; i <= m ; i++) {
		while(!wait.empty() && t[i].x >= wait.top().first) {
			leisure.push(wait.top().second);
			wait.pop();
		}
		if(leisure.empty()) continue;
		int Top = leisure.top();
		leisure.pop();
		res[Top] ++;
		wait.push(make_pair(t[i].y , Top));
	}
	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 + 1 + m1 , mycmp);
	sort(b + 1 , b + 1 + m2 , mycmp);
	Work(a , m1 , res1);
	Work(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;
}

你可能感兴趣的:(NOIP,c++,算法)