CSP-S 2021题解

T1 廊桥分配

题目链接 P7913 [CSP-S 2021] 廊桥分配

\(\mathbf{Lemma}:\) 对于已经存在的廊桥集合,加入新的廊桥不会影响之前分配好的航班。

证明: 由于题目所给限制,若有空廊桥,则航班一定会停在空廊桥,直至该航班离开,廊桥再次空置,下一个符合条件的航班使用该廊桥。有空置廊桥不必讨论;而在所有廊桥都被占满的期间,显然加入新的廊桥不会影响已经停靠的航班。

而国内和国际航班是独立的,因此分别讨论即可。维护一个航班的序列,枚举廊桥个数 \(1\)~\(n\)\(0\) 不必枚举,因为必然没有飞机停靠在廊桥)。设已经分配了 \(k\) 个廊桥,每当廊桥个数 \(+1\),即加入一个空廊桥(显然不影响已经分配好的 \(k\) 个廊桥和航班),将剩余未停靠航班中最先到的航班取出并使它“停靠在廊桥”,然后删除该航班,继续枚举下一个符合条件的航班并从航班序列中删除,直至不存在符合条件的航班。

这样做可以看作维护了很多个“航班集合”,这些航班集合满足了时间区间互不相交,且按抵达时间排列。

一种实现方法如下:

void count(int n, int *cnt) { // cnt[k] 表示k个廊桥可以停靠的航班数
	for(int i = 1; i <= n; ++i) {
		int pos = 0, idx = 0;
                // s为现存航班序列,按照左端点排序(为什么?请读者思考)
		while(true) {
			it = s.lower_bound(flight(pos, 0));
			if(it == s.end()) break;
			pos = (*it).r;
			s.erase(it);
			++idx;
		}
		cnt[i] = cnt[i - 1] + idx;
	}
}

分别处理国内和国际航班得到两个 \(cnt\) 数组,答案即为 \(\max \limits_{i=0}^{n}cnt_i+cnt^{'}_{n-i}\)

注意坑点 \(i\)\(0\) 开始枚举而不是 \(1\),因为国内(或国际)航班的廊桥个数可以为 \(0\)

完整代码见文末(请勿抄袭)。

T2 括号序列

(留用)

T3 回文

题目链接 P7915 [CSP-S 2021] 回文

\(\mathbf{Lemma}:\) 原题等价于将所给的长度为 \(2n\) 的序列划分为两个长度为 \(n\) 的序列,其中一个序列连续,将序列下标记为 \([1,\mathrm{head}]\cup[\mathrm{tail},2n]\)(记为序列 \(1\))和 \((\mathrm{head},\mathrm{tail}\)(记为序列 \(2\)),且满足以下几条性质:
\(\hspace{3em}\)\(1.\) \(\mathrm{tail}-\mathrm{head} - 1=n\)
\(\hspace{3em}\)\(2.\) 当序列 \(1\) 按照某一种删除方式组成回文数列前 \(n\) 项,在序列 \(2\) 中存在唯一确定的方式删除并组成回文数列的 \(n-1\)~\(2n\) 项。
\(\hspace{2em}\)无法做到时,无解。

证明:首先,原题要求按照取出所给序列的首项或末项加入新的空序列,直至原序列空,且要求新序列回文。由于每次只能取出首项或末项,由于对称性,第一次取出第 \(1\) 项或第 \(2n\) 项操作相同。假设第一次取出的为第 \(1\)\(a_1\),则在剩余的 \(2\)~\(2n\) 项中,必然值存在与 \(a_1\) 相同的一项(题目指出这 \(2n\) 个数,\(1\)~\(n\) 各出现两次),则该项必定最后输出,且倒数第二次输出的一定在该项的左侧 \(1\) 个或右侧 \(1\) 个(如果存在),故以此类推,最后输出的 \(n\) 的操作对应的数在序列中必然连续,性质 \(2\) 得证;而性质 \(1\) 显然不必证明。

由上述引理,我们的目标转化为寻找符合条件的 \(\mathrm{head}\)\(\mathrm{tail}\)

而由证明可以得到,(第先输出左起第一个元素为例,对称)先找到与第一个元素相同的元素位置 \(\mathrm{pos}\),设置 \(\mathrm{head}=\mathrm{pos}-1\),且 \(\mathrm{tail}=\mathrm{pos}+1\)。设置两个指针 \(l=2,r=2n\),每次优先比较 \(a_l\)\(a_{\mathrm{head}},a_{\mathrm{tail}}\) 是否相等,再比较 \(a_r\)\(a_{\mathrm{head}},a_{\mathrm{tail}}\) 是否相等,(如果比较合法),这是因为要求字典序最小。则 \(l\)\(\mathrm{head}\)\(r\)\(\mathrm{tail}\) 的大小关系分别有 \(3\) 种,共 \(9\) 种情况,下面依次讨论。

\(\mathbf{Explaination1:}\)\(a_l\) 满足合法条件时,\(l\) 自加 \(1\);当 \(a_r\) 满足合法条件时,\(r\) 自减 \(1\)。且两者仅有唯一一者满足。

\(\mathbf{Explaination2:}\)\(a_{\mathrm{head}}\) 满足合法条件时,\(\mathrm{head}\) 自减 \(1\);当 \(a_{\mathrm{tail}}\) 满足合法条件时,\(\mathrm{tail}\) 自加 \(1\),且两者仅有唯一一者满足。

\(\mathbf{Explaination3:}\) \(l\)\(r\) 的一次变动必然伴随着 \(\mathrm{head}\)\(\mathrm{tail}\) 的一次变动。

\(1.\) \(l<\mathrm{head}\)\(r>\mathrm{tail}\),四种比较均可行;

\(2.\) \(l=\mathrm{head}\)\(r>\mathrm{tail}\),此时 \(head\) 可以左移一位(表示将当前元素 \(a_l\) 划分在序列 \(2\) 中),\(l\) 可以右移一位(表示将当前元素 \(a_{\mathrm{head}}\) 划分在序列 \(1\) 中),但可见 \(a_l\)\(a_{\mathrm{head}}\) 的比较不合法;

\(3.\) \(l<\mathrm{head}\)\(r=\mathrm{tail}\),同上,\(a_r\)\(a_{\mathrm{tail}}\) 的比较不合法;

\(4.\) \(l>\mathrm{head}\)\(r>\mathrm{tail}\),此时,可见 \(1\)~\(head\) 的枚举已经全部完成,只有 \(a_r\)\(a_{\mathrm{tail}}\) 的比较仍然合法;

\(5.\) \(l<\mathrm{head}\)\(r<\mathrm{tail}\),同上,只有 \(a_l\)\(a_{\mathrm{head}}\) 的比较合法;

\(6.\) \(l=\mathrm{head}\)\(r=\mathrm{tail}\),可以证明此时区间 \([\mathrm{head},\mathrm{tail}]\) 必然包含 \(n+1\) 个元素,由于前面的比较全部合法,因此 \(a_{\mathrm{head}}=a_{\mathrm{tail}}\),证明见下文。此时由于要使得字典序最小,所以优先输出 \(\text{L}\),即序列 \(2\) 对应下标区间为 \((\mathrm{head},\mathrm{tail+1})\),此时视作 \(l,\mathrm{tail}\) 合法。

\(7.\) \(l>\mathrm{head}\)\(r<\mathrm{tail}\),枚举全部完成,序列 \(1\)\(2\) 均存在。

\(8.\) \(l>\mathrm{head}\)\(r=\mathrm{tail}\),可以证明不存在此情况,证明见下文。

\(9.\) \(l=\mathrm{head}\)\(r<\mathrm{tail}\),同上。

\(\hspace{2em}\)对于情况 \(6\) 的证明:由 \(\mathbf{Explaination3}\) 可知 \(\mathrm{head/tail}\)\(l/r\) 的变动次数相等。因此 \(\mathrm{tail}-\mathrm{head}-2=(l-2)+(2n-r)\),且 \(l=\mathrm{head}, r=\mathrm{tail}\),故 \(\mathrm{tail}-\mathrm{head}=n\),即 \([\mathrm{head},\mathrm{tail}]\) 包含 \(n+1\) 个元素;
\(\hspace{2em}\)下面证明 \(a_{\mathrm{head}}=a_{\mathrm{tail}}\)。假设两者不相等,而下标为序列 \(2\)\(n+1\) 个元素中,必然存在两个相等的元素。若这两个元素位于 \((\mathrm{head},\mathrm{tail})\) 内,由操作合法性知,无法实现(不能越过相同元素而使得 \(\mathrm{head}\)\(\mathrm{tail}\) 达到现在位置),则必然有一个元素(设位置为 \(i\))处于 \((\mathrm{head},\mathrm{tail})\) 内与 \(a_{\mathrm{head}}\)\(a_{\mathrm{tail}}\) 相等。那么同样的,对于不等的元素位置(假设为 \(a_{\mathrm{head}}\)),则:① \(i<pos\) 时,\(\mathrm{head}\) 无法越过 \(i\) 达到现在位置;②\(i>pos\) 时,\(\mathrm{tail}\) 无法越过 \(i\) 达到现在位置。综上,假设不成立,\(a_{\mathrm{head}}=a_{\mathrm{tail}}\)

\(\hspace{2em}\)对于情况 \(8\)\(9\) 的证明(以情况 \(8\) 为例):同对情况 \(6\) 的证明,由于上述操作必然有 \(l-\mathrm{head} = 1\) (两者情况转化而来:①\(l=\mathrm{head}-1\)时,\(l\) 自加 \(1\)\(\mathrm{head}\) 自减 \(1\);②\(l=head\) 时,\(l\) 自加 \(1\)\(\mathrm{head}\) 自减 \(1\))。因此 \(\mathrm{tail}-\mathrm{head}-2=(l-2)+(2n-r)\),即 \(2(\mathrm{tail}-\mathrm{head})-1=2n\),奇数等于偶数,显然不成立。

综上,经多次 \(1-7\) 个情况必然能找到合法序列,且每次只会满足一种情况。在该情况下,若不存在合法操作,则无解。

此时,我们只需规定第一次取最左端还是最右端,跑两次即可。

//a储存输入序列,c储存操作字符
//main()中
for(int i = 2; i <= 2 * n; ++i) {
	if(a[i] == a[1]) head = i - 1, tail = i + 1;
}
c[1] = 'L';
flag = solve(n, 2, r, head, tail)
// ...

for(int i = 2; i <= 2 * n; ++i) {
	if(a[i] == a[2 * n]) head = i - 1, tail = i + 1;
}
c[1] = 'R';
flag = solve(n, 1, r - 1, head, tail)
// ...

其中,\(\text{solve}\) 函数实现如下(模拟情况 \(1-7\)):

bool solve(int n, int l, int r, int &head, int &tail) {
	bool flag = 1; int idx = 1;
	while(flag) {
		if(l < head and r > tail) {
			if(a[l] == a[head]) {
				c[++idx] = 'L', l++, head--;
			} else if(a[l] == a[tail]) {
				c[++idx] = 'L', l++, tail++;
			} else if(a[r] == a[head]) {
				c[++idx] = 'R', r--, head--;
			} else if(a[r] == a[tail]) {
				c[++idx] = 'R', r--, tail++;
			} else flag = 0;
		} else if(l == head and r > tail) {
			if(a[l] == a[tail]) {
				c[++idx] = 'L', l++, tail++;
			} else if(a[r] == a[tail]) {
				c[++idx] = 'R', r--, tail++;
			} else if(a[r] == a[head]) {
				c[++idx] = 'R', r--, head--;	
			} else flag = 0;
		} else if(l > head and r > tail) {
			if(a[r] == a[tail])
				c[++idx] = 'R', r--, tail++;
			else flag = 0;
		} else if(l < head and r == tail) {
			if(a[l] == a[head]) {
				c[++idx] = 'L', l++, head--;
			} else if(a[l] == a[tail]) {
				c[++idx] = 'L', l++, tail++;
			} else if(a[r] == a[head]) {
				c[++idx] = 'R', r--, head--;
			} else flag = 0;
		} else if(l == head and r == tail) {
			c[++idx] = 'L', tail++;
			break;
		} else if(l < head and r < tail) {
			if(a[l] == a[head]) 
				c[++idx] = 'L', l++, head--;
			else flag = 0;
		} else break;
	}
	return flag;
}

仅供参考,完整代码见文末(请勿抄袭)。

T4 交通规划

(留用)

各题完整代码

T1 廊桥分配

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

struct flight {
	int l, r;
	bool operator < (const flight &x) const {
		return l < x.l;
	}
	flight(int a, int b) : l(a), r(b) {}
};

set<flight> s;
set<flight> :: iterator it;

const int N = 1e5 + 5;
int cnt1[N], cnt2[N];

void count(int n, int *cnt) {
	for(int i = 1; i <= n; ++i) {
		int pos = 0, idx = 0;
		while(true) {
			it = s.lower_bound(flight(pos, 0));
			if(it == s.end()) break;
			pos = (*it).r;
			s.erase(it);
			++idx;
		}
		cnt[i] = cnt[i - 1] + idx;
	}
}

int main() {
	int n, m1, m2, ans = 0;
	scanf("%d %d %d", &n, &m1, &m2);
	for(int i = 1, x, y; i <= m1; ++i)
		scanf("%d %d", &x, &y), s.insert(flight(x, y));
	count(n, cnt1);
	s.clear();
	for(int i = 1, x, y; i <= m2; ++i)
		scanf("%d %d", &x, &y), s.insert(flight(x, y));
	count(n, cnt2);
	for(int i = 0; i <= n; ++i)
		ans = max(ans, cnt1[i] + cnt2[n - i]);
	printf("%d\n", ans);
	return 0;
}

T3 回文

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

const int N = 1e6 + 5;
int a[N];
char c[N];

bool solve(int n, int l, int r, int &head, int &tail) {
	bool flag = 1; int idx = 1;
	while(flag) {
		if(l < head and r > tail) {
			if(a[l] == a[head]) {
				c[++idx] = 'L', l++, head--;
			} else if(a[l] == a[tail]) {
				c[++idx] = 'L', l++, tail++;
			} else if(a[r] == a[head]) {
				c[++idx] = 'R', r--, head--;
			} else if(a[r] == a[tail]) {
				c[++idx] = 'R', r--, tail++;
			} else flag = 0;
		} else if(l == head and r > tail) {
			if(a[l] == a[tail]) {
				c[++idx] = 'L', l++, tail++;
			} else if(a[r] == a[tail]) {
				c[++idx] = 'R', r--, tail++;
			} else if(a[r] == a[head]) {
				c[++idx] = 'R', r--, head--;	
			} else flag = 0;
		} else if(l > head and r > tail) {
			if(a[r] == a[tail])
				c[++idx] = 'R', r--, tail++;
			else flag = 0;
		} else if(l < head and r == tail) {
			if(a[l] == a[head]) {
				c[++idx] = 'L', l++, head--;
			} else if(a[l] == a[tail]) {
				c[++idx] = 'L', l++, tail++;
			} else if(a[r] == a[head]) {
				c[++idx] = 'R', r--, head--;
			} else flag = 0;
		} else if(l == head and r == tail) {
			c[++idx] = 'L', tail++;
			break;
		} else if(l < head and r < tail) {
			if(a[l] == a[head]) 
				c[++idx] = 'L', l++, head--;
			else flag = 0;
		} else break;
	}
	return flag;
}

void output(int n, int head, int tail) {
	int r = head++, l = tail--;
	for(int i = 1; i <= n; ++i) putchar(c[i]);
	for(int i = n; i; --i) {
		if(c[i] == 'L') {
			if(a[r] == a[head]) {
				putchar('L'), r--, head++;
			} else putchar('R'), r--, tail--;
		} else {
			if(a[l] == a[head]) {
				putchar('L'), l++, head++;
			} else putchar('R'), l++, tail--;
			
		}
	}
	puts("");
}

int main() {
//	freopen("palin3.in", "r", stdin);
//	freopen("palin3.out", "w", stdout);
	int T, n, r, head, tail;
	bool flag = 1;
	scanf("%d", &T);
	while(T--) {
		scanf("%d %d", &n, a + 1), r = (n << 1);
		for(int i = 2; i <= r; ++i) {
			scanf("%d", a + i);
			if(a[i] == a[1]) head = i - 1, tail = i + 1;
		}
		a[r + 1] = 0, c[1] = 'L';
		if(!(flag = solve(n, 2, r, head, tail))) {
			for(int i = 1; i < r; ++i) 
				if(a[r] == a[i]) {
					head = i - 1, tail = i + 1;	
					break;	
				}		 
			c[1] = 'R';
			if(!(flag = solve(n, 1, r - 1, head, tail))) puts("-1");
			else output(n, head, tail);
		} else output(n, head, tail);
	}
	return 0;
}
posted @ 2022-09-18 19:19  Ning-H  阅读(82)  评论(0编辑  收藏  举报