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\)

posted @ 2026-08-15 16:56  Loyal_Soldier  阅读(10)  评论(0)    收藏  举报