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] >= midb[i + 1] >= mid - 1,那么不一定是 b[i + 1] >= midb[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;
}

本题总结

本题核心在于几个部分

  1. 找到单调性,想到二分答案
  2. 能写出初步思路代码
  3. 根据初步思路代码来证明贪心结论

G - 回文匹配

初始思路

一开始想到了暴力,即枚举区间 \([l,r]\),统计区间中间有多少个子串;找子串的过程可以用 KMP 来优化,区间子串个数可以通过二维前缀和,时间复杂度 \(O(n^2)\)

赛后题解

由于 manacher 可以求出每个位置的最长回文子串,设求包含 \([l, r]\) 的子串,所以问题就转化成了(a 表示匹配的起点)

\[(a_l+\cdots+a_r)+(a_{l+1}+\cdots+a_{r-1}+)+(a_{l+2}+\cdots+a_{r-2})+\cdots \]

发现式子分成了左右两部分,现在只考虑左半边,右半边是一样的

\[\sum_{i=l}^{r}a_i\times(i-l+1) = \sum_{i=l}^{r}(a_i\times i)-(\sum_{i=l}^{r}a_i)\times l + \sum_{i=l}^{r}a_i \]

所以只要考虑 \(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;
}

本题总结

本题核心在于几个部分

  1. 找到答案求出的数据的关系
  2. 能想到用 manacher 来优化回文串部分
  3. 能推出前缀和的式子
  4. 能用前缀和来优化区间和

本题几个实现细节

  1. \(2^{32}\) 取模其实就是用 unsigned int 来取模。
  2. 不需要在 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;
}

本题总结

本题核心在于几个部分

  1. 找到单调性,想到二分答案
  2. 能找到合法的条件
  3. 利用数据结构来维护

I - Distance to Different

初始思路

NULL

赛后题解

考虑把序列按照联通块分段,例如

\[[1,1,2,3,3,3,3,1] \]

可以分成(令 \(S_n(x)\) 表示 \(x\) 出现了 \(n\) 次)

\[[S_2(1),S_1(2),S_4(3),S_1(1)] \]

转化成 \(b\) 数组就是

\[2, 1 \]

\[1 \]

\[1,2,2,1 \]

\[1 \]

观察到除了首尾两项,其他都是回文的,这很好理解,但不重要。

其实对于相同位置的 \(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;
}

本题总结

本题核心在于几个部分

  1. 找到数量和内容无关的性质。
  2. 能转化题目变为问题转化成了把长度为 \(n\) 的区间划分为不少于 \(k\) 段且中间不能为 \(2\) 的方案数
  3. 能用前缀和优化 DP。
posted @ 2026-08-10 22:19  AKCoder  阅读(42)  评论(0)    收藏  举报