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;
}

术劣(sequence)

posted @ 2025-06-01 14:28  Zctf1088  阅读(39)  评论(0)    收藏  举报