2025CSP-S模拟赛9 比赛总结
2025CSP-S模拟赛9
T1 皮胚 (match)
这一题其实是简单题。
这题没做出来只能说是我活该。
比较简单。首先不难写出一个神秘复杂度的暴力 dfs,然后就可以顺理成章的把这个 dfs 变成 dp。
至于为什么没做出来,我认为应当是这学期太颓了,训练得太少了,脑子变迟钝了。这是一个很经典的套路(甚至我以前经常用),暑假一定要好好练。
#include <bits/stdc++.h>
using namespace std;
const int N = 2005;
int n, m;
char s[N], t[N];
int vis[N];
void dfs(int x, int y, char lst) {
if (y == m) {
vis[x] = 1;
return;
}
if (s[x + 1] == t[y + 1]) {
dfs(x + 1, y + 1, s[x + 1]);
} else if (t[y + 1] == '.') {
dfs(x + 1, y + 1, s[x + 1]);
} else if (t[y + 1] == '*') {
dfs(x, y + 1, lst);
for (int i = 1; s[x + i] == lst; i++) {
dfs(x + i, y + 1, lst);
}
}
}
bitset<26> f[N][N];
int solve() {
scanf("%s%s", s + 1, t + 1);
n = strlen(s + 1), m = strlen(t + 1);
for (int i = 1; i <= n; i++) vis[i] = 0;
// dfs(0, 0, '?');
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++) f[i][j] = 0;
f[0][0][0] = 1;
for (int i = 0; i <= n; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < 26; k++) {
if (!f[i][j][k]) continue;
if (s[i + 1] == t[j + 1]) {
f[i + 1][j + 1][s[i + 1] - 'a'] = 1;
} else if (t[j + 1] == '.') {
f[i + 1][j + 1][s[i + 1] - 'a'] = 1;
} else if (t[j + 1] == '*') {
f[i][j + 1][k] = 1;
for (int t = 1; s[i + t] - 'a' == k; t++) {
f[i + t][j + 1][k] = 1;
}
}
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++) ans += f[i][m][s[i] - 'a'];
printf("%d\n", ans);
return 0;
}
int main() {
int qq;
scanf("%d", &qq);
while (qq--) {
solve();
}
return 0;
}
T2 核冰 (merge)
40pts
有一个贪心。
我们考虑把这些数全部存到一个桶里,然后从小到大依次处理每一个值。如果当前这个值的数量大于 2,那么就把多于 1 的部分全部合并(也就是最后只剩一个或两个剩下的全部合并),这样一定是最优的。
然后就是 \(O(1)\) 修改,\(O(n)\) 查询。时间复杂度 \(O(nm)\)。
100pts
考虑用线段树维护贪心。
我们拿一棵权值线段树(桶)把数列存起来,然后跑一遍这个贪心。另外再记录一下每个点的合并的次数(后面会用)。
查询就是 \(O(1)\) 直接查询。
修改就相当于减去一个原来的数,添加一个改之后的数。这里就会有一些问题。
先考虑减去一个原来的数。这里就会出现一种情况,就会使这个贪心坏掉。比如说我原序列是 \(2,2,2,4\),我贪心玩之后应该是 \(2,3,4\)。然而我现在如果把原序列中的第一个 \(2\) 改成 \(3\),直接在桶上做减法的话就会得到 \(3,3,4\),这样显然没有不进行任何操作的 \(2,2,3,4\) 优。此时就需要从 \(3\) 取回来一个加到 \(2\) 上。此时记录的合并次数就用了。抛开这个例子。在把 \(3\) 放回 \(2\) 后,\(3\) 上也可能会出现类似的情况,重复此过程即可。然而时间复杂度可以达到 \(O(n^2\log n)\),显然不可取。解决方法是二分出最后一个应当进行放回操作的点(线段树二分),然后区间修改即可。
添加一个改之后的数类似。需要判断是否加上后需要进行合并(大于 3),然后类似的进行二分并区间操作即可。
然后统计答案的话,只需要在单点操作时动态维护全局变量即可。
我的代码就是屎山,又臭又长,看看就行了。
#include <bits/stdc++.h>
using namespace std;
int read() {
int x = 0; char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x;
}
const int INF = 0x3f3f3f3f;
const int N = 5e5 + 100;
int n, qq, a[N], m;
int ans;
struct node {
int l, r, s, mx, c1, c0, lazy;
} tree[4 * N];
struct node1 {
int l, r, s, mn, lazy;
} tree1[4 * N];
#define lc p << 1
#define rc p << 1 | 1
//SEG1
void add(int p, int v) {
tree[p].s += v * (tree[p].r - tree[p].l + 1);
tree[p].lazy += v;
tree[p].mx += v;
if (v & 1) swap(tree[p].c1, tree[p].c0);
}
void pushup(int p) {
tree[p].s = tree[lc].s + tree[rc].s;
tree[p].mx = max(tree[lc].mx, tree[rc].mx);
tree[p].c1 = tree[lc].c1 & tree[rc].c1;
tree[p].c0 = tree[lc].c0 & tree[rc].c0;
}
void pushdown(int p) {
if (!tree[p].lazy) return;
add(lc, tree[p].lazy);
add(rc, tree[p].lazy);
tree[p].lazy = 0;
}
void build(int p, int l, int r) {
tree[p].l = l, tree[p].r = r;
if (l == r) return;
int mid = l + r >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
}
void update(int p, int x, int v) {
if (tree[p].l == tree[p].r) {
tree[p].s += v;
tree[p].mx += v;
if (tree[p].s & 1) {
tree[p].c1 = 1;
tree[p].c0 = 0;
} else {
tree[p].c1 = 0;
tree[p].c0 = 1;
}
return;
}
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if (x <= mid) update(lc, x, v);
else update(rc, x, v);
pushup(p);
}
int query(int p, int x) {
if (tree[p].l == tree[p].r) return tree[p].s;
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if (x <= mid) return query(lc, x);
else return query(rc, x);
}
void update_range(int p, int l, int r, int v) {
if (l > r) return;
if (l == tree[p].l && r == tree[p].r) {
add(p, v);
return;
}
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if (r <= mid) update_range(lc, l, r, v);
else if (l > mid) update_range(rc, l, r, v);
else update_range(lc, l, mid, v), update_range(rc, mid + 1, r, v);
pushup(p);
}
int query_range(int p, int l, int r) {
if (l > r) return 0;
if (tree[p].l == l && r == tree[p].r) return tree[p].s;
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if (r <= mid) return query_range(lc, l, r);
else if (l > mid) return query_range(rc, l, r);
else return query_range(lc, l, mid) + query_range(rc, mid + 1, r);
}
//SEG2
void add1(int p, int v) {
tree1[p].s += v * (tree1[p].r - tree1[p].l + 1);
tree1[p].mn += v;
if (tree1[p].lazy == INF) tree1[p].lazy = v;
else tree1[p].lazy += v;
}
void pushup1(int p) {
tree1[p].s = tree1[lc].s + tree1[rc].s;
tree1[p].mn = min(tree1[lc].mn, tree1[rc].mn);
}
void pushdown1(int p) {
if (tree1[p].lazy > 1e9) return;
add1(lc, tree1[p].lazy);
add1(rc, tree1[p].lazy);
tree1[p].lazy = INF;
}
void build1(int p, int l, int r) {
tree1[p].l = l, tree1[p].r = r;
if (l == r) return;
int mid = l + r >> 1;
build1(lc, l, mid), build1(rc, mid + 1, r);
}
void update1(int p, int x, int v) {
if (tree1[p].l == tree1[p].r) {
tree1[p].s += v;
tree1[p].mn += v;
return;
}
pushdown1(p);
int mid = tree1[p].l + tree1[p].r >> 1;
if (x <= mid) update1(lc, x, v);
else update1(rc, x, v);
pushup1(p);
}
int query1(int p, int x) {
if (tree1[p].l == tree1[p].r) return tree1[p].s;
pushdown1(p);
int mid = tree1[p].l + tree1[p].r >> 1;
if (x <= mid) return query1(lc, x);
else return query1(rc, x);
}
void update1_range(int p, int l, int r, int v) {
if (l > r) return;
if (l == tree1[p].l && r == tree1[p].r) {
add1(p, v);
return;
}
pushdown1(p);
int mid = tree1[p].l + tree1[p].r >> 1;
if (r <= mid) update1_range(lc, l, r, v);
else if (l > mid) update1_range(rc, l, r, v);
else update1_range(lc, l, mid, v), update1_range(rc, mid + 1, r, v);
pushup1(p);
}
int query1_range(int p, int l, int r) {
if (l > r) return 0;
if (tree1[p].l == l && r == tree1[p].r) return tree1[p].s;
pushdown1(p);
int mid = tree1[p].l + tree1[p].r >> 1;
if (r <= mid) return query1_range(lc, l, r);
else if (l > mid) return query1_range(rc, l, r);
else return query1_range(lc, l, mid) + query1_range(rc, mid + 1, r);
}
//two find
int fid1(int p, int x) {
if (tree[p].mx < 2) return INF;
if (tree[p].l >= x && tree[p].s == 2 * (tree[p].r - tree[p].l + 1)) {
return tree[p].l;
}
if (tree[p].l == tree[p].r) return INF;
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if (x > mid) return fid1(rc, x);
int res = fid1(lc, x);
return res == INF ? fid1(rc, x) : res;
}
int fid(int p, int x) {
if (tree1[p].mn > 0) return INF;
if (tree[p].l >= x && tree1[p].s == 0) {
return tree[p].l;
}
if (tree[p].l == tree[p].r) return INF;
pushdown1(p);
int mid = tree[p].l + tree[p].r >> 1;
if (x > mid) return fid(rc, x);
int res = fid(lc, x);
return res == INF ? fid(rc, x) : res;
}
int fid2(int p, int x) {
if (tree[p].l == x && tree[p].c0 && tree[p].s == 2 * (tree[p].r - tree[p].l + 1)) {
return tree[p].r;
}
if (tree[p].l == tree[p].r) return -1;
pushdown(p);
pushdown1(p);
int mid = tree[p].l + tree[p].r >> 1;
if (x > mid) return fid2(rc, x);
int res = fid2(lc, x);
if (res < mid) return res;
return max(res, fid2(rc, mid + 1));
}
//---
void print() {
cout << "T1 "; for (int i = 1; i <= n + 5; i++) cout << query(1, i) << " "; cout << "\n";
cout << "T2 "; for (int i = 1; i <= n + 5; i++) cout << query1(1, i) << " "; cout << "\n";
}
int main() {
// freopen("data3_01.in", "r", stdin);
// freopen("merge3.in", "r", stdin);
// freopen("mine.out", "w", stdout);
n = read(), qq = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
}
m = N - 1;
build(1, 1, m);
for (int i = 1; i <= n; i++) {
update(1, a[i], 1);
}
build1(1, 1, m);
for (int i = 1; i <= m; i++) {
int num = query(1, i);
if (num > 0) {
ans++;
int tmp = (num - 1) % 2 + 1;
if (num != tmp) {
update(1, i, -(num - tmp));
update(1, i + 1, (num - tmp) / 2);
update1(1, i, (num - tmp) / 2);
}
}
}
while (qq--) {
int op = read();
if (op == 1) {
int id = read(), y = read();
int x = a[id];
update(1, x, -1);
if (query(1, x) == 0) ans--;
if (query(1, x) == 0 && query1(1, x) > 0) {
int p = min(fid1(1, x + 1), fid(1, x + 1));
if (p == INF) p = x;
update(1, x, 2);
ans++;
update_range(1, x + 1, p - 1, 1);
update(1, p, -1);
if (query(1, p) == 0) ans--;
update1_range(1, x, p - 1, -1);
}
if (query(1, y) == 0) ans++;
update(1, y, 1);
if (query(1, y) == 3) {
int p = fid2(1, y + 1);
if (p == -1) p = y;
update(1, y, -2);
update_range(1, y + 1, p, -1);
if (query(1, p + 1) == 0) ans++;
update(1, p + 1, 1);
update1_range(1, y, p, 1);
}
a[id] = y;
} else {
printf("%d\n", ans);
}
}
return 0;
}
方珍(merge)
10pts
首先有一个暴力,直接爆算 mex,时间复杂度 \(O(n^3)\)。考试时不知道为什么写写假了。
30pts
考虑二分。二分 \(\text{mex}\),然后 \(O(n)\) check 一下这个 mex 是否在前 \(k\) 个。二分答案。
如何判断?考虑统计 \(\text{mex}<mid\) 的区间个数 \(cnt\),若 \(cnt<k\),则二分的这个 \(mid\) 就在前 \(k\) 个,然后就可以继续往后二分。具体的,考虑双指针,考虑枚举 \(r\),并维护 \(l\),使得 \(\text{mex}\{i,r\}<mid(i \in \forall[l,r])\),然后就可以统计了。
时间复杂度 \(O(n^2\log n)\)。
100pts
考虑将所有的数组按照 \(w_i\) 降序排列,然后以此计算。维护一个大的变量 \(ans\),每次对于一个数组,只需要 check \((ans+1-w_i)\),若合法则 \(ans+1\)。由于 \(w_i\) 递减,所以 \(ans\) 最多增加 \(n\) 次。
时间复杂度 \(O(n^2)\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int n, a[N];
struct node {
int k, w, m;
unsigned long long seed;
bool operator < (const node & cmp) const {
return w > cmp.w;
}
} s[N];
unsigned long long seed;
unsigned long long _rand(){
seed^=seed<<13;
seed^=seed>>7;
seed^=seed<<17;
return seed;
}
int t[N];
int aaa;
bool check(int num, int k, int m) {
if (num <= 0) return true;
for (int i = 0; i < m; i++) t[i] = 0;
int sum = 0, l = 1, cnt = 0;
for (int r = 1; r <= n; r++) {
if (a[r] < num) cnt += (t[a[r]]++ == 0);
while (cnt >= num) {
while (a[l] >= num) l++;
cnt -= (--t[a[l]] == 0);
l++;
}
sum += r - l + 1;
if (sum >= k) return false;
}
return sum < k;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d%d%llu", &s[i].k, &s[i].w, &s[i].m, &s[i].seed);
}
sort(s + 1, s + 1 + n);
int ans = 0;
for (int i = 1; i <= n; i++) {
seed = s[i].seed;
for (int j = 1; j <= n; j++) a[j] = _rand() % s[i].m;
ans = max(ans, s[i].w);
while (check(ans + 1 - s[i].w, s[i].k, s[i].m)) ans++;
}
printf("%d\n", ans);
return 0;
}

浙公网安备 33010602011771号