2202.10.11 CSP-S 2021 测试总结

2022.10.11 CSP-S 2021 测试总结

这场打的好心累, \(T1\) 想了 \(1\) 个多小时才想出来的, \(T2\)\(T4\)题意赛时还没读明白。

\(T1\)廊桥分配

\(T1\) 感觉是这几道题里最水的一道题,暴力都能踩过,然而却想了 \(1\) 个多小时的优化。首先我们考虑它是按照先来后到的顺序,那么一定会按照来的时间进行排序,那么很容易想到 \(O(n^2)\) 的做法,我们可以用 \(sum_i\) 代表在分配给 \(i - 1\) 个廊桥后又分配了一个所多飞机数,那么我们每次只需要暴力的找到每个廊桥是否符合当前的飞机即可。但是数据给到了 \(1e5\),怎么优化呢?我们可以用一个 \(set\),将每个飞机到达的时间扔到 \(set\) 里,然后我们在用一个 \(map\) 存一下每个飞机的离开时间即可,这样我们就可以二分查找到符合的飞机了,并且很容易删除掉符合的飞机。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 1e5 + 7;
set<int> s , ss;
unordered_map<int,int> a , b;
int sum1[M] , sum2[M];
signed main () {
	freopen("airport.in","r",stdin);
	freopen("airport.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	int n , m1 , m2;
	cin >> n >> m1 >> m2;
	int cnt = 0 ,sum =0 ;
	for(int i = 1; i <= m1; ++ i) {
		int l , r; cin >> l >> r;
		a[l] = r;
		s.insert(l);	
	}
	s.insert(0x3f3f3f3f);
	while(cnt < m1)	{
		sum ++;
		int x;
		for(auto i : s) {
			x = i;
			break;
		}
		while(x < 1e9) {
			sum1[sum] ++;
			cnt ++;
			s.erase(x);
			x = a[x];
			x = *s.lower_bound(x);
		}
	}
	for(int i = 1; i <= n; ++ i) sum1[i] += sum1[i - 1];
	cnt = 0 , sum = 0;
	for(int i = 1; i <= m2; ++ i) {
		int l , r; cin >> l >> r;
		b[l] = r;
		ss.insert(l);	
	}
	ss.insert(0x3f3f3f3f);
	while(cnt < m2)	{
		sum ++;
		int x;
		for(auto i : ss) {
			x = i;
			break;
		}
		while(x < 1e9) {
			sum2[sum] ++;
			cnt ++;
			ss.erase(x);
			x = b[x];
			x = *ss.lower_bound(x);
		}
	}
	int ans = 0;
	for(int i = 1; i <= n; ++ i) sum2[i] += sum2[i - 1];
	for(int i = 0; i <= n; ++ i) ans = max(ans , sum1[i] + sum2[n - i]);//这里是从0开始!
	cout << ans << '\n';
}

\(T2\)括号序列

\(T2\) 好难。赛时的时候打了个爆搜结果发现题读假了,然后果断放弃了。等作者写出来再来补吧。补出来了(看的题解)。这道题的难点在于读题和对于合法串的 \(check\),首先我们考虑什么样的串是合法的,如 \((*(*)*)\) 就不是一个合法的序列,\((*(*)(*)*)\)也不是,而 \((*(*)*(*))\) 则是一个合法的序列,为什么呢,因为我们可以将 \((*)*(*)\) 看做一个合法序列 \(A\),则变成了 \((*A)\),这样他就合法了,而前两种都无法通过转化变成这种。那怎么做呢。首先很容易想到爆搜,每次试填,复杂度大概是 \(O(3^n)\) 的,那怎么办采取优化呢,我们发现 \(n \le 500\),很像一个区间 \(dp\),那我们是否可以采取类似于区间 \(dp\) 的方法呢。答案是肯定的。我们首先要明确几种状态,下面用数字代替了

	0:****...***** 
	编号为0的是全是 * 的情况
	1:(.........)
	编号为1是外面套一个括号的情况
	2:(...)**..**(..)**..**
	编号为2是以括号开头,以 * 结尾的情况
	3:(..)**..**(..)**..**(..)
	编号为3是以括号开头,以括号结尾的情况
	4:**(..)**..**(..)
	编号为4是以 * 开头以括号结尾的情况
	5:**(..)**(..)**
	编号为5是以 * 开头并结尾的情况(0是特殊的5的情况)

这样我们就可以很容易的进行 \(dp\) 设计了,我们令 \(f_{i,j,k}\)\(l\) ~ \(r\) 区间内为编号 \(k\) 的方案数。我么可以得到转移方程:

\(f_{i,j,0}\)

直接特判即可

\(f_{i,j,1}\) \(=\) \(f_{i+1,l-1,0} + f_{i+1,l-1,2}+f_{i+1,l-1,3}+f_{i+1,l-1,4}\)

但是我们需要特判一下是否可以嵌套上括号

\(f_{i,j,2}\) \(=\) \(\sum_{l=i}^{j-1}f_{i,l,3} * f_{l + 1,0}\)

\(f_{i,j,3}\) \(=\) \(\sum_{l=i}^{j-1}(f_{i,l,3}+f_{i,l,2})*f_{l+1,j,1}+f_{i,j,1}\)

注意编号为 \(1\) 也是以括号开头和结尾的,也需要加上

\(f_{i,j,4}\) \(=\) \(\sum_{l=i}^{j-1}(f_{i,l,4}+f_{i,l,5})*f_{l+1,j,0}\)

\(f_{i,j,5}\) \(=\) \(\sum_{l=i}^{j-1}f_{i,l,4}*f_{l+1,j,0}\)

然后放一个简短的代码吧

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 5e2 + 7 , mod = 1e9  +7;;
int f[M][M][6];
char s[M];
bool check(int l , int r) {return (s[l] == '(' || s[l] == '?') && (s[r] == ')' || s[r] == '?');}
signed main () {
	int n , m; cin >> n >> m;
	cin >> s + 1;
	for(int i = 1; i <= n; ++ i) f[i][i - 1][0] = 1;
	for(int i = 1; i <= n; ++ i) {
		for(int j = 1; j <= n - i + 1; ++ j ){
			int k = j + i - 1;
			if(i <= m) f[j][k][0] = f[j][k - 1][0] && (s[k] == '*' || s[k] == '?');
			if(i >= 2) {
				if(check(j , k)) f[j][k][1] = (f[j + 1][k - 1][0] + f[j + 1][k - 1][2] + f[j + 1][k - 1][4] + f[j + 1][k - 1][3]) % mod;	
				for(int l = j; l <= k - 1; ++ l) {
					f[j][k][2] = (f[j][k][2] + f[j][l][3] * f[l + 1][k][0] % mod) % mod;
					f[j][k][3] = (f[j][k][3] + (f[j][l][2] + f[j][l][3]) * f[l + 1][k][1]% mod) % mod;
					f[j][k][4] = (f[j][k][4] + (f[j][l][4] + f[j][l][5]) * f[l + 1][k][1]% mod) % mod;
					f[j][k][5] = (f[j][k][5] + f[j][l][4] * f[l + 1][k][0] % mod) % mod;
				}
			}
			f[j][k][5] = (f[j][k][5] + f[j][k][0]) % mod;
			f[j][k][3] = (f[j][k][3] + f[j][k][1]) % mod;
		}
	}
	cout << f[1][n][3];
}

\(T3\)回文

\(T3\)\(T2\) 简单太多了。但是作者太弱了,赛时只想出来了一个爆搜。对于 \(a\) 数组,我们最先开始只能掐头或去尾,那么我们的第一个操作就知道了,下面我们只考虑第一步取开头的情况,我们设开头数字为 \(x\),我们考虑回文串的性质,那么最后一个操作是将另一个开头或结尾的数字扔到 \(b\) 数组里,所以我们考虑找到第二个 \(x'\),这样我们只需要在 \(x\)\(x'\) 中间插入其他数就可以了。那么如何维护其他数呢?因为两个一样的数不能连续取,所以一定有一个在前一个在后,所以我们可以考虑用栈来处理,我们可以将在 \(x'\) 左边的数放到第一个栈 \(s_1\) ,右边的放到第二个栈 \(s_2\),那么我们会发现如果一个数是两个栈的栈顶,另一个在栈底,那么他们两个可以组成回文。我们只需每次判断栈顶是否符合,然后将栈底上移即可。但题目中又要求字典序最小,所以我们优先做 \(x'\) 左侧的,但是对于在不同的两个栈的数,我们会让取右侧的数尽量在后即可。

view code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 1e6 + 7;
int a[M] , s1[M] , s2[M] , top1 , top2 , vis[M] , last1 , last2 , ans[M];
signed main () {
	freopen("palin2.in","r",stdin);
	freopen("out.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t; cin >> t;
	while(t -- ) {
		int n; cin >> n;
		top1 = 0 , top2 = 0;
		last1 = 1 , last2 = 1;
		for(int i = 1; i <= 2 * n; ++ i) cin >> a[i];
		int pos;
		for(int i = 2; i <= 2 * n; ++ i) {
			if(a[i] == a[1]) {pos = i; break;}
		}
		for(int i = pos - 1; i > 1 ; -- i) s1[++ top1] = a[i];
		for(int i = pos + 1; i <= 2 * n; ++ i) s2[++ top2] = a[i];
		int cnt = 1;
		while(cnt <= n) {
			if(s2[last2] == s1[top1] && last2 <= top2 && top1 >= last1) {
				last2 ++;
				ans[++ cnt] = 1 , ans[2 * n - cnt + 1] = 2;
				top1 --;
			}
			else if(s1[last1] == s1[top1] && top1 > last1) {
				last1 ++;
				ans[++ cnt] = 1 , ans[2 * n - cnt + 1] = 1;
				top1 --;	
			}
			else if(s2[last2] == s2[top2] && last2 < top2) {
				last2 ++;
				ans[++ cnt] = 2 , ans[2 * n - cnt + 1] = 2;
				top2 --;
			}
			else if(s1[last1] == s2[top2] && top2 >= last2 && last1 <= top1) {
				last1 ++;
				ans[++ cnt] = 2 , ans[2 * n - cnt + 1] = 1;
				top2 --;
			}
			else break;
		}
		if(cnt == n) {
			ans[1] = 1;
			ans[2 * n] = 1;
			for(int i = 1; i <= 2 *  n; ++ i) 
				if(ans[i] == 1) cout << 'L';
				else if(ans[i] == 2) cout << 'R';
			cout << '\n';
			continue;	
		}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		top1 = 0 , top2 = 0;
		last1 = 1 , last2 = 1;
		for(int i = 1; i <= 2 * n; ++ i) {
			if(a[i] == a[n * 2]) {pos = i; break;}
		}
		for(int i = pos - 1; i > 0 ; -- i) s1[++ top1] = a[i];
		for(int i = pos + 1; i < 2 * n; ++ i) s2[++ top2] = a[i];
		cnt = 1;
		while(cnt <= n) {
			if(s2[last2] == s1[top1] && last2 <= top2 && top1 >= last1) {
				last2 ++;
				ans[++ cnt] = 1 , ans[2 * n - cnt + 1] = 2;
				top1 --;
			}
			else if(s1[last1] == s1[top1] && top1 > last1) {
				last1 ++;
				ans[++ cnt] = 1 , ans[2 * n - cnt + 1] = 1;
				top1 --;	
			}
			else if(s2[last2] == s2[top2] && last2 < top2) {
				last2 ++;
				ans[++ cnt] = 2 , ans[2 * n - cnt + 1] = 2;
				top2 --;
			}
			else if(s1[last1] == s2[top2] && top2 >= last2 && last1 <= top1) {
				last1 ++;
				ans[++ cnt] = 2 , ans[2 * n - cnt + 1] = 1;
				top2 --;
			}
			else break;
		}
		if(cnt == n) {
			ans[1] = 2;
			ans[2 * n] = 1;
			for(int i = 1; i <= 2 * n; ++ i)
				if(ans[i] == 1) cout << 'L';
				else if(ans[i] == 2) cout << 'R';
			cout << '\n';
			continue;	
		}
		cout << -1 << '\n';
	}
}

\(T4\)交通规划

\(T4\) 就算了吧,我不配了。

posted @ 2022-10-11 18:09  L3067545513  阅读(46)  评论(0编辑  收藏  举报