2021 CSP 题解报告
写在前面
由于最近模拟赛实在是太多了,这篇题解报告拖到了 NOIP 考前 3 天才打算写 /kk
A 廊桥分配
solution
因为来的飞机一定会占领闲置的编号最小的廊桥,可以考虑维护一个当前闲置廊桥的编号堆和没有飞走的飞机的离开时间堆,这样就能求出每架飞机在哪个廊桥停靠,对于每个廊桥求出停靠在这里的飞机数量,求个前缀和,每个廊桥编号所对应的停靠的飞机数目就是分配这些廊桥能停靠的飞机数目。
然后枚举分配廊桥的所有方案,取个最大值就好了。
复杂度 \(O(nlogn)\)
code
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9' ) {if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, m1, m2, ans1[MAXN], ans2[MAXN];
struct Plane{int s, t;}a[MAXN], b[MAXN];
struct Node{
int id, t;//占用的廊桥标号和离开的时间
bool operator < (const Node &rhs)const {return t > rhs.t;}
};
bool cmp(Plane x, Plane y) {return x.s < y.s;}
priority_queue<int, vector<int>, greater<int> >q;//闲置的廊桥
priority_queue<Node>Q;
int main() {
n = read(), m1 = read(), m2 = read();
for (int i = 1; i <= m1; i++) a[i].s = read(), a[i].t = read();
for (int i = 1; i <= m2; i++) b[i].s = read(), b[i].t = read();
sort(a + 1, a + m1 + 1, cmp), sort(b + 1, b + m2 + 1, cmp);
for (int i = 1; i <= n; i++) q.push(i);
for (int i = 1; i <= m1; i++) {
while(Q.size() && Q.top().t <= a[i].s) {//该离开的飞机离开
q.push(Q.top().id);
Q.pop();
}
if(q.size()) {//看这个飞机用哪个廊桥
int id = q.top(); q.pop();
Q.push((Node){id, a[i].t});
ans1[id]++;
}
}
while(!q.empty()) q.pop();
while(!Q.empty()) Q.pop();
for (int i = 1; i <= n; i++) q.push(i);
for (int i = 1; i <= m2; i++){
while(Q.size() && Q.top().t <= b[i].s) {
q.push(Q.top().id);
Q.pop();
}
if(q.size()) {
int id = q.top(); q.pop();
Q.push((Node){id, b[i].t});
ans2[id]++;
}
}
for (int i = 1; i <= n; i++) ans1[i] += ans1[i - 1], ans2[i] += ans2[i - 1];
int Ans = 0;
for (int i = 0; i <= n; i++) Ans = max(ans1[i] + ans2[n - i], Ans);
cout<<Ans;
return 0;
}
B 括号序列
solution
\(dp\)
思路来自 wsyear
状态
-
\(dp_{i, j, 0}\) 形如
**...**的括号序列(即全是*) -
\(dp_{i, j, 1}\) 形如
(...)的括号序列。(即左右直接被括号包裹且最左边括号与最右边的括号相互匹配)。 -
\(dp_{i,j,2}\): 形态如
(...)**(...)***的括号序列(即左边以括号序列开头,右边以*结尾)。 -
\(dp_{i,j,3}\): 形态如
(...)***(...)*(...)的括号序列(即左边以括号序列开头,右边以括号序列结尾,注意:第 2 种形态也属于这种形态)。 -
\(dp_{i, j, 4}\): 形态如
***(...)**(...)的括号序列(即左边以*开头,右边以括号序列结尾)。 -
\(dp_{i,j,5}\) : 形态如
***(...)**(...)**的括号序列(即左边以*开头,右边以*结尾,注意:第 1 种形态也属于这种形态)。
转移
\(dp_{i, j, 0}\) 直接判就好了。
\(dp_{i, j, 1} = (dp_{l+1,r−1,0}+dp_{l+1,r−1,2}+dp_{l+1,r−1,3}+dp_{l+1,r−1,4})\times compare(l,r)\)
\(dp_{l,r,2}=\sum\limits_{i=l}^{r-1} dp_{l,i,3}\times dp_{i + 1, r, 0}\)
\(dp_{l, r, 3} = dp_{l,r,3}=\sum\limits_{i=l}^{r-1} (dp_{l,i,2}+dp_{l,i,3})\times dp_{i+1,r,1}+dp_{l,r,1}\)
\(dp_{l,r,4}=\sum\limits_{i=l}^{r-1} (dp_{l,i,4}+dp_{l,i,5})\times dp_{i+1,r,1}\)
\(dp_{l,r,5}=\sum\limits_{i=l}^{r-1} dp_{l,i,4}\times dp_{i+1,r,0}+dp_{l,r,0}\)
答案就是 \(dp_{1, n, 3}\)
复杂度 \(O(n^3)\)
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 510;
const int mod = 1e9 + 7;
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9' ) {if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, k;
int dp[MAXN][MAXN][6];
char s[MAXN];
bool compare(int i, int j) {
return (s[i] == '?' || s[i] == '(') && (s[j] == '?' || s[j] == ')');
}
signed main() {
n = read(), k = read();
scanf("%s", s + 1);
for (int i = 1; i <= n; i++) dp[i][i - 1][0] = 1;
for (int len = 1; len <= n; len++) {
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
if(len <= k && (s[j] == '?' || s[j] == '*')) dp[i][j][0] = dp[i][j - 1][0];
if(len >= 2) {
if(compare(i, j)) dp[i][j][1] = (dp[i][j][1] + dp[i + 1][j - 1][0] + dp[i + 1][j - 1][2] + dp[i + 1][j - 1][3] + dp[i + 1][j - 1][4]) % mod;
for (int k = i; k <= j - 1; k++) {
dp[i][j][2] = (dp[i][j][2] + dp[i][k][3] * dp[k + 1][j][0] % mod) % mod;
dp[i][j][3] = (dp[i][j][3] + (dp[i][k][2] + dp[i][k][3]) * dp[k + 1][j][1] % mod) % mod;
dp[i][j][4] = (dp[i][j][4] + (dp[i][k][4] + dp[i][k][5]) * dp[k + 1][j][1] % mod) % mod;
dp[i][j][5] = (dp[i][j][5] + (dp[i][k][4] * dp[k + 1][j][0]) % mod) % mod;
}
}
dp[i][j][5] = (dp[i][j][5] + dp[i][j][0]) % mod;
dp[i][j][3] = (dp[i][j][3] + dp[i][j][1]) % mod;
}
}
printf("%lld", dp[1][n][3]);
return 0;
}
C 回文
solution
结论:当一个取得位置确定的时候,最后一个取得位置也固定了,那么整个序列就会被分成两部分
只需要维护四个指针,在序列首,在序列尾,在最后取的数的左边和右边。
然后按照字典序尽可能小的情况下取就好了。
code
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9' ) {if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int T, n, a[MAXN], Ans[MAXN], ll, rr, tot, pos;
int fag;
void work(int l, int r, int L, int R) {
ll = 1, rr = 2 * n, tot = 0;
if(R == 2 * n) Ans[ll++] = 1, Ans[rr--] = 1;
else Ans[ll++] = 2, Ans[rr--] = 1;
while(ll <= rr && tot < 2 * n) {
if(a[L] == a[l] && L < l) {
Ans[ll++] = 1, Ans[rr--] = 1;
L++, l--, tot += 2;
}
else if(a[L] == a[r] && L <= l && r <= R) {
Ans[ll++] = 1, Ans[rr--] = 2;
L++, r++, tot += 2;
}
else if(a[R] == a[l] && R >= r && l >= L){
Ans[ll++] = 2, Ans[rr--] = 1;
R--, l--, tot += 2;
}
else if(a[R] == a[r] && r < R) {
Ans[ll++] = 2, Ans[rr--] = 2;
R--, r++, tot += 2;
}
else break;
}
}
int main() {
T = read();
while(T--) {
memset(Ans, 0, sizeof Ans);
n = read();
for (int i = 1; i <= 2 * n; i++) a[i] = read();
for (int i = 2; i <= 2 * n; i++)
if(a[i] == a[1]) {pos = i; break;}
if(T == 1) fag = 1;
work(pos - 1, pos + 1, 2, 2 * n);
fag = 0;
if(tot + 2 == 2 * n) {
for (int i = 1; i <= 2 * n; i++) {
if(Ans[i] == 1) cout<<"L";
else cout<<"R";
}
puts("");
continue;
}
memset(Ans, 0, sizeof Ans);
for (int i = 1; i < 2 * n; i++)
if(a[i] == a[2 * n]) {pos = i; break;}
work(pos - 1, pos + 1, 1, 2 * n - 1);
if (tot + 2 == 2 * n) {
for (int i = 1; i <= 2 * n; i++) {
if (Ans[i] == 1) cout<<"L";
else cout<<"R";
}
puts("");
continue;
}
puts("-1");
}
return 0;
}
D 交通运输
网络流忘干净了,这个 \(Ariel\) 真是菜菜菜菜啊。

浙公网安备 33010602011771号