20260810 杂题总结
一、比赛基本情况
- 比赛日期:2026-08-10
- 比赛时长:3h
- 比赛得分:5.15 pts
- 比赛排名:rk 7
二、比赛整体回顾
T1 到 T5 相对来说比较简单,属于 CSP-J 300pts 水准。
T6 ~ T7 属于 CSP-S 第 \(1\) 题水准,T8 ~ T9 属于 CSP-S \(2.5\) 题水平。
主要原因在于写 T1 时,编译器出现了问题,然后卡了 10 min 才恢复,下次注意检查电脑情况。
回文匹配 这道题以前出现过,但是没有通过,每次考试过后一定要补题。
确实思维太差了啊!要多练。
三、题目复盘
F - Greedy Gift Takers P
初始思路
通过搓样例观察到如果第 \(i\) 个位置已经不能得到礼物,则第 \(i+1\) 个位置也得不到礼物。
然后初步想到 check 可以判断是否可以走过 mid 这个点,但是造了几组数据后发现并不对。
然后就放到一边了。
考场代码
bool check(int mid) {
int totmid = mid;
memcpy(b, a, sizeof(a));
for (int i = 1; i < totmid; i++) {
if (b[i] < mid) return false;
--mid;
}
return true;
}
赛后题解
通过在初始思路上进行贪心。
如果存在一个 b[i] < mid 时不可行,那么也就是对于所有的 b[i] >= mid。
因为 mid 所对应的函数是减函数,所对应的 b[i] 也要尽量递减,所以从大到小排序一定最优,证明可以用邻项交换法来证明,即如果 b[i] >= mid 且 b[i + 1] >= mid - 1,那么不一定是 b[i + 1] >= mid 且 b[i] >= mid - 1,所以这样子最优。
复杂度
- 时间复杂度:\(O(n\log^2n)\)。
- 空间复杂度:\(O(n)\)。
补题代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 1e5 + 7;
constexpr int P = 998244353;
int n, a[N], b[N];
bool check(int mid) {
int totmid = mid;
memcpy(b, a, sizeof(a));
sort(b + 1, b + totmid, greater<int>());
for (int i = 1; i < totmid; i++) {
if (b[i] < mid) return false;
--mid;
}
return true;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), a[i] = n - a[i];
int L = 1, R = n;
while (L < R) {
int mid = (L + R + 1) / 2;
if (check(mid)) L = mid;
else R = mid - 1;
}
printf("%d\n", n - L);
return 0;
}
本题总结
本题核心在于几个部分
- 找到单调性,想到二分答案
- 能写出初步思路代码
- 根据初步思路代码来证明贪心结论
G - 回文匹配
初始思路
一开始想到了暴力,即枚举区间 \([l,r]\),统计区间中间有多少个子串;找子串的过程可以用 KMP 来优化,区间子串个数可以通过二维前缀和,时间复杂度 \(O(n^2)\)。
赛后题解
由于 manacher 可以求出每个位置的最长回文子串,设求包含 \([l, r]\) 的子串,所以问题就转化成了(a 表示匹配的起点)
发现式子分成了左右两部分,现在只考虑左半边,右半边是一样的
所以只要考虑 \(a_i \times i\) 和 \(a_i\) 的前缀和就好了。
复杂度
- 时间复杂度:\(O(n+m)\)。
- 空间复杂度:\(O(n+m)\)。
补题代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 3e6 + 7;
constexpr int P = 998244353;
int n, m, nxt[N], f[N], p[N], val[N];
char s[N], t[N];
unsigned int presum[N], presmul[N];
void next_algorithm() {
int j = 0;
nxt[1] = 0;
for (int i = 2; i <= m; i++) {
while (j && t[i] != t[j + 1]) j = nxt[j];
if (t[i] == t[j + 1]) ++j;
nxt[i] = j;
}
}
void manacher() {
int M = 0, R = 0;
for (int i = 1; i <= n; i++) {
if (i > R) p[i] = 1;
else p[i] = min(p[M * 2 - i], R - i + 1);
while (i - p[i] >= 0 && i + p[i] <= n && s[i + p[i]] == s[i - p[i]]) ++p[i];
if (i + p[i] - 1 > R) {
R = i + p[i] - 1;
M = i;
}
}
}
void kmp() {
next_algorithm();
int j = 0;
for (int i = 1; i <= n; i++) {
while (j && s[i] != t[j + 1]) j = nxt[j];
if (s[i] == t[j + 1]) ++j;
f[i] = j;
if (j == m) {
j = nxt[j];
++val[i - m + 1];
}
}
}
int main() {
scanf("%d%d", &n, &m);
scanf("%s", s + 1);
scanf("%s", t + 1);
if (n < m) return puts("0"), 0;
kmp();
manacher();
// Σ p[i] * (i - l + 1) = Σ (p[i] * i) - Σ p[i] * l + Σ p[i]
for (int i = 1; i <= n; i++) {
presum[i] = presum[i - 1] + val[i];
presmul[i] = presmul[i - 1] + (unsigned int)val[i] * i;
}
unsigned int ans = 0;
for (int i = 1; i <= n; i++) {
int l = i - p[i] + 1, r = i + p[i] - m;
if (l > r) continue;
int mid = (l + r) / 2;
ans += (presmul[mid] - presmul[l - 1]) - (presum[mid] - presum[l - 1]) * l + (presum[mid] - presum[l - 1]);
if (mid != r)
ans += (presum[r] - presum[mid]) * r + (presum[r] - presum[mid]) - (presmul[r] - presmul[mid]);
}
printf("%u\n", ans);
return 0;
}
本题总结
本题核心在于几个部分
- 找到答案和求出的数据的关系
- 能想到用 manacher 来优化回文串部分
- 能推出前缀和的式子
- 能用前缀和来优化区间和
本题几个实现细节
- 对 \(2^{32}\) 取模其实就是用
unsigned int来取模。 - 不需要在 manacher 使用不存在的字符。
H - Level Up
初始思路
没想到二分,而是想了半天线段树,其实也是可以做的,只不过需要懒标记加一些操作。
赛后题解
可以观察到,参数越小越容易逃跑,具有单调性,可以二分;如果小于阈值,就会逃跑,否则会打架。
逃跑的充要条件是在 \(i\) 之前就已经大于 \(a_i\),即至少打了 \(a_ik\) 个怪兽。
我们要统计 \(k\) 以前是否有 \(a_ik\) 的怪兽,可以用树状数组维护。
复杂度
- 时间复杂度:\(O(n\log^2n)\)。
- 空间复杂度:\(O(n)\)。
补题代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 2e5 + 7;
constexpr int P = 998244353;
int n, q, a[N], tr[N], limit[N];
void modify(int x, int v) {
for (; x < N; x += (x & -x)) tr[x] += v;
}
int query(int x) {
int res = 0;
for (; x; x -= (x & -x)) res += tr[x];
return res;
}
bool check(int x, int mid) {
return (ll)x * mid <= query(mid);
}
int main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) {
int L = 1, R = n;
while (L < R) {
int mid = (L + R) / 2;
if (check(a[i], mid)) L = mid + 1;
else R = mid;
}
limit[i] = L;
modify(L, 1);
}
while (q--) {
int i, x;
scanf("%d%d", &i, &x);
puts(x >= limit[i] ? "YES" : "NO");
}
return 0;
}
本题总结
本题核心在于几个部分
- 找到单调性,想到二分答案
- 能找到合法的条件
- 利用数据结构来维护
I - Distance to Different
初始思路
NULL
赛后题解
考虑把序列按照联通块分段,例如
可以分成(令 \(S_n(x)\) 表示 \(x\) 出现了 \(n\) 次)
转化成 \(b\) 数组就是
观察到除了首尾两项,其他都是回文的,这很好理解,但不重要。
其实对于相同位置的 \(S_n(x)\),输出的 \(b\) 都是一样的,所以内容是什么就不重要了。
因此问题转化成了把长度为 \(n\) 的区间划分为不少于 \(k\) 段的方案数。
但对于中间长度为 \(2\) 的部分要特殊考虑,因为如果这么计数,\(\{1,1\}\) 和 \(\{2\}\) 会被考虑成一种,算的时候减去就可以了。
还有最后答案说不小于 \(k\),但只需要考虑到 \(k\) 就好了,剩下的叠加到 \(k + 1\) 上,注意这里要分别特判一下。
直接做会 TLE,观察是一个关于前缀的式子,所以可以前缀和优化。
复杂度
- 时间复杂度:\(O(nk)\)。
- 空间复杂度:\(O(nk)\)。
补题代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 2e5 + 7;
constexpr int P = 998244353;
int n, k;
ll sum[N][15], dp[N][24];
int main() {
scanf("%d%d", &n, &k);
sum[0][0] = dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
sum[i][0] = 1;
for (int j = 1; j <= k; j++) {
dp[i][j] = sum[i - 1][j - 1];
if (i > 2 && i < n) dp[i][j] = (dp[i][j] - dp[i - 2][j - 1] + P) % P;
sum[i][j] = (sum[i][j] + sum[i - 1][j] + dp[i][j]) % P;
}
dp[i][k + 1] = (sum[i - 1][k] + sum[i - 1][k + 1]) % P;
if (i > 2 && i < n) dp[i][k + 1] = (dp[i][k + 1] - dp[i - 2][k] - dp[i - 2][k + 1] + P) % P;
sum[i][k + 1] = (sum[i - 1][k + 1] + dp[i][k + 1]) % P;
}
printf("%lld\n", (dp[n][k] + dp[n][k + 1]) % P);
return 0;
}
本题总结
本题核心在于几个部分
- 找到数量和内容无关的性质。
- 能转化题目变为问题转化成了把长度为 \(n\) 的区间划分为不少于 \(k\) 段且中间不能为 \(2\) 的方案数。
- 能用前缀和优化 DP。

浙公网安备 33010602011771号