CSP-S 真题做题记录
注:均为 \(2020\) 年及以后。
[CSP-S 2020 T2] 动物园
T1 打磨你被我吃掉了,真好吃/吃糖。
先找出所有被至少一个动物编号覆盖的二进制位。
如果某个二进制位对应一种饲料,但该位没有被现有的动物覆盖,则新增的动物编号不能覆盖该位(否则会导致出现新的饲料类型)。
#include <bits/stdc++.h>
#define int unsigned long long
using namespace std;
int n, m, c, k;
int yuan, shen;
signed main() {
cin >> n >> m >> c >> k;
for (int i = 1; i <= n; i++) {
int a;
cin >> a;
yuan |= a;
}
for (int i = 1; i <= m; i++) {
int p, q;
cin >> p >> q;
if (!(yuan >> p & 1)) shen |= 1ll << p;
}
int ans = 1;
for (int i = 0; i < k; i++)
if (!(shen >> i & 1)) ans <<= 1;
if (!ans && !n) {
cout << "18446744073709551616";
return 0;
}
cout << ans - n;
return 0;
}
[CSP-S 2020 T3] 函数调用
首先观察到,题目中的函数调用构成一个 DAG,很可能是拓扑排序。
从“不含第 \(2\) 类函数或不含第 \(1\) 类函数”入手,“函数调用关系构成一棵树”貌似没什么用,因为入度为 \(1\) 没卵用。
只有乘操作
只需要算出乘了多少次,建反图拓扑排序,因为一个原来出度为 \(0\) 的点就是乘操作,会向他入度的点贡献。
只有加
一个点被加的次数就相当于一个点到达的次数,也就是从起点到该点的方式总数,通过拓扑排序求出每个加函数加的次数。
如果一个函数调用后序列 \(\times k\),相当于这个函数执行了 \(k\) 次。
那么把加转换成乘,但是注意全局乘本身对答案影响。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e5 + 10;
const int mod = 998244353;
int in1[MAXN], in2[MAXN];
vector <int> G1[MAXN], G2[MAXN];
int n, m, q;
int a[MAXN], op[MAXN], p[MAXN], add[MAXN], mul[MAXN], cnt[MAXN];
void topu1() {
queue <int> que;
for (int i = 0; i <= m; i++) {
if (!in1[i]) {
que.push(i);
}
}
while (!que.empty()) {
int u = que.front();
que.pop();
for (auto v : G1[u]) {
mul[v] = mul[v] * mul[u] % mod;
if (!(--in1[v])) {
que.push(v);
}
}
}
}
void topu2() {
queue <int> que;
for (int i = 0; i <= m; i++) {
if (!in2[i]) {
que.push(i);
}
}
while (!que.empty()) {
int u = que.front();
int now = 1;
que.pop();
for (int i = G2[u].size() - 1; i >= 0; i--) {
int v = G2[u][i];
cnt[v] = (cnt[v] + now * cnt[u] % mod) % mod;
now = now * mul[v] % mod;
if (!(--in2[v])) {
que.push(v);
}
}
}
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> op[i];
if (op[i] == 1) {
cin >> p[i] >> add[i];
mul[i] = 1;
} else if (op[i] == 2) {
cin >> mul[i];
} else {
int c;
cin >> c;
while (c--) {
int g;
cin >> g;
G1[g].push_back(i);
G2[i].push_back(g);
in1[i]++;
in2[g]++;
}
mul[i] = 1;
}
}
mul[0] = 1;
cnt[0] = 1;
cin >> q;
for (int i = 1; i <= q; i++) {
int f;
cin >> f;
G1[f].push_back(0);
G2[0].push_back(f);
in1[0]++;
in2[f]++;
}
topu1();
topu2();
for (int i = 1; i <= n; i++) {
a[i] = a[i] * mul[0] % mod;
}
for (int i = 1; i <= m; i++) {
if (op[i] == 1) {
a[p[i]] = (a[p[i]] + cnt[i] * add[i] % mod) % mod;
}
}
for (int i = 1; i <= n; i++) {
cout << a[i] << ' ';
}
return 0;
}
[CSP-S 2021 T1] 廊桥分配
由于只有两种,可以枚举第一种用了多少个廊桥,考虑用了 \(x\) 个廊桥的停靠飞机数怎么做。
考虑每有一个新飞机,则加入当前空闲廊桥队列中最小的廊桥,发现这样是正确的,因为可以将 \(\ge n + 1\) 都看成远机位。
那么在枚举过程中用一个前缀和计算即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e5 + 10;
int n, m1, m2;
struct node {
int a;
int l;
}a[MAXN], b[MAXN];
bool cmp(node a, node b) {
return a.a < b.a;
}
int sum1[MAXN], sum2[MAXN];
void solve(int *sum, node *a, int m) {
priority_queue <int, vector <int>, greater <int>> cb;
priority_queue <pair <int, int>, vector <pair <int, int>>, greater <pair <int, int>>> plane;//第一个为当前飞机离开时间,第二为占用廊桥
for (int i = 1; i <= n; i++) {
cb.push(i);
}
for (int i = 1; i <= m; i++) {
while (!plane.empty() && plane.top().first <= a[i].a) {
cb.push(plane.top().second);
plane.pop();
}
if (!cb.empty()) {
int qwq = cb.top();
sum[qwq]++;
cb.pop();
plane.push({a[i].l, qwq});
}
}
for (int i = 1; i <= n; i++) {
sum[i] += sum[i - 1];
}
}
signed main() {
cin >> n >> m1 >> m2;
for (int i = 1; i <= m1; i++) {
cin >> a[i].a >> a[i].l;
}
for (int i = 1; i <= m2; i++) {
cin >> b[i].a >> b[i].l;
}
sort(a + 1, a + m1 + 1, cmp);
sort(b + 1, b + m2 + 1, cmp);
solve(sum1, a, m1);
solve(sum2, b, m2);
int ans = 0;
for (int i = 0; i <= n; i++) {
ans = max(ans, sum1[i] + sum2[n - i]);
}
cout << ans;
return 0;
}
/*
如果没有廊桥限制则,则每有一个新飞机,则加入当前空闲廊桥队列中最小的廊桥
有的话就是初始空闲廊桥队列为 1~n
对两个飞机数组分别模拟这个过程,用一个数组 sum 记录第 i 个廊桥停靠飞机数,做个前缀和
sum[i] 相当于 i 个廊桥停靠飞机数,求出这个就做出来了
飞机要按抵达时间排序
*/
[CSP-S 2021 T2] 括号序列
这种显然考虑区间 \(dp\)

浙公网安备 33010602011771号