2022.10.11 CSP-S 2021 测试总结
这场打的好心累, \(T1\) 想了 \(1\) 个多小时才想出来的, \(T2\),\(T4\)题意赛时还没读明白。
\(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\) 好难。赛时的时候打了个爆搜结果发现题读假了,然后果断放弃了。等作者写出来再来补吧。补出来了(看的题解)。这道题的难点在于读题和对于合法串的 \(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\) 就算了吧,我不配了。