[Record] CSP-S 2025 邮寄 + 题解
邮寄
upd: T2 T3 都被卡常了。T2 是因为本地内存访问极快导致对自己常数的错误评估,T3 有同学比我少写 20 pts 部分分其余做法一致最终比我高 20 pts 左右,不知缘由。算了,属于是我自己菜。
也不算特别寄,但是出考场之后 \(\texttt{T3}\) \(\texttt{T4}\) 都胡出来了,就很难绷。
总的来说我这人太懒了。 最最基础的 trie 没做几道,双树贡献做的一两道题被遗忘在了记忆的角落(但若是提醒起来,jzp 讲课的措辞我都还记得请),暑假练的计数 dp 上的容斥之类的已经被脑子里的水、氧气、二氧化碳浸泡了许久,蒙上了厚厚一层四处透风却又难以去除的 \(\text{Cu}_2\text{(OH)}_2\text{CO}_3\)。
(我也不知道铜元素由何产生,但或许 [GCJ 2019 #2] Contransmutation 这道题可以描述它的来历。)
但还有二十几天时间。Confidence works miracles.
- \(\texttt{T1}\) 水题,读完题写写就过了。
- \(\texttt{T2}\) 胡思乱想了一会,然而并没有想到比 \(O(m\log m+nk2^k\alpha(n))\) 更好的做法。看了看今年的机器配置,不想搓大数据测速。两道题一共没花多久。
- \(\texttt{T3}\) 一眼 \(\texttt{70 pts}\),看样子是简单题,然而卡住了,观察到 \(\sum (T_1+T_2)\) 这种限制便开始想根号分治,于是死活想不到上 Trie。离考试结束还有三个小时,却不受控制地感到紧张,手一直在抖,持续到考试结束后一两个小时都未好转。浪费了很久时间,最后稳住心态打完 \(\texttt{70 pts}\) 后拼上一个凑出来的乱搞,大样例 \(\texttt{0.1s}\) 左右,跑了。
- \(\texttt{T4}\) 打了个状压。做法是套路的,但凡我训练题贡献系数反推部分做过一道都会果断开题,属于是遭到了我这种懒人应有的报应。但是换一个思路,容斥 dp 暑假练过 [ZJOI2022] 树 ,总该想到吧?然而看到 \(c_i>k\) 这种明显的下界限制我甚至没有想过容斥。
胜负无常。
抽象的是有男生考试考到发懵去上了个女厕所。我走进厕所后倒退出来,望着偌大的“女”字,看着对面的男生,陷入了沉思。我想起 [JOISC 2016 Day2] 如厕计划——我也会想起它在 LOJ 上的名称叫做女装大佬。
美式咖啡的酸和意式浓缩的苦交织,考完后一夜未眠,其实我很害怕,害怕在考试还剩下三个小时的时候双手莫名的颤抖,也害怕是不是以后也会在自己明明会做的题目上脑子犯轴。
周天返校便听闻班上有人 AK 了。
仿刘克庄国脉篇填了两阙贺新郎。
听尽黄昏鼓。指当今,英雄无数,功名三五。虽是人间风波恶,剑起寒芒谁主。敢问我,可合尺度?既有少年能作赋,也不甘不往攀天路。那畏怕,龙和虎。
疾风堪试飞新羽。叹从前,拍遍栏杆,时机频误。勋业从来非空有,只待朝夕争取。已看过,几番艰阻。棋柝何曾逢对手,怎蹉跎劳力怜佳句。事未定,休言负。
Deepseek 的赏析挺有趣的,“蹉跎劳力怜佳句”我只是字面意思,它说,这是关于不要陷入局部最优的自警。
也是,十年磨一剑,雕花铭文可暂且不看,总该要试一试凝着冰霜的刃口是否锋利。
题解
前两题还需要写吗?
T3 [CSP-S 2025] 谐音替换 / replace
我真是脑子有病。
听 lyt 说题解区有个根号做法,看了下和我考场思路是一致的,但考试时我认为这个做法非常劣。其他题解没看,从考场思路改出来的 \(O(L\log L)\) 的做法可能比较繁了。
考察可替换的一个必要条件,对于每组字符串 \((S_1,S_2)/(T_1,T_2)\) 考虑第一个不相同位置到最后一个不相同位置之间的子段 \(M\),只有子段完全相同的 \(S\) 可能可以对 \(T\) 进行互换。
将每个字符串记为 \(P+M+Q\),则另一个必要条件是 \(S=P_S+M+Q_S\) 能对 \(T=P_T+M+Q_T\) 进行替换仅当 \(P_S\) 为 \(P_T\) 的后缀,\(Q_S\) 为 \(Q_T\) 的前缀。
对所有 \(P\) 的反串和所有 \(Q\) 分别建出一棵 \(\text{trie}\),按照双树问题的一般套路,在 \(P\) 反串建出的 \(\text{trie}\) 上进行 \(\text{dfs}\) 扫描线,在 \(Q\) 的 \(\text{trie}\) 上树状数组维护子树加单点查。
Code
当代码写得太丑时,不压行的节操终究会被舍弃。
#include <bits/stdc++.h>
using namespace std;
typedef long long i64;
constexpr int N = 2e5+5;
constexpr int M = 5e6+5;
constexpr int B = 131;
constexpr int MOD = 998244853;
typedef pair<int, int> pint;
typedef string Str;
#define fi first
#define se second
#define mk make_pair
#define eb emplace_back
#define low lower_bound
#define FOR(t,l,r) for (int t = l; t <= r; t++)
#define REV(t,r,l) for (int t = r; t >= l; t--)
int n, q, rst[N];
Str S1[N], S2[N], T1[N], T2[N];
int al[N], ar[N], bl[N], br[N];
pint a[N], b[N];
int f1[N], f2[N];
vector<pint> arr;
bool gethash(Str& s1, Str& s2, int& l, int& r, pint& c) {
cin >> s1 >> s2;
if (s1.size() != s2.size()) return 0;
l = -1, r = -1;
FOR(i, 0, (int)s1.size()-1) if (s1[i] != s2[i]) (!~l)&&(l = i), r = i;
if (!~l) return 0;
int val = 0;
FOR(i, l, r) val = ((i64)val*B*B+(s1[i]-'a'+1)*B+(s2[i]-'a'+1))%MOD;
arr.eb(c = mk(r-l, val));
return 1;
}
vector<int> buc[N][2];
int tree[M], lim;
#define lowbit(x) ((x)&(-x))
void update(int l, int r, int v) {
for (int x = l; x <= lim; x += lowbit(x)) tree[x] += v;
for (int x = r+1; x <= lim; x += lowbit(x)) tree[x] -= v;
}
int query(int x) {
int rst = 0;
while (x) { rst += tree[x]; x -= lowbit(x); }
return rst;
}
vector<pint> vec[M][2];
vector<int> tmp;
int dfn[M], out[M], timer;
struct Trie {
int trie[M][26], tot;
void init(int x) { __builtin_memset(trie[x], 0, sizeof trie[x]); }
void dfs(int x) {
dfn[x] = ++timer;
FOR(i, 0, 25) if (trie[x][i]) dfs(trie[x][i]);
out[x] = timer;
}
int insert(int x, int c) {
if (trie[x][c]) return trie[x][c];
trie[x][c] = ++tot;
return init(tot), tot;
}
void getans(int x) {
for (auto i : vec[x][0]) update(dfn[i.fi], out[i.fi], 1);
for (auto i : vec[x][1]) rst[i.se] = query(dfn[i.fi]);
FOR(i, 0, 25) if (trie[x][i]) getans(trie[x][i]);
for (auto i : vec[x][0]) update(dfn[i.fi], out[i.fi], -1);
}
} t1, t2;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
freopen("replace.in", "r", stdin);
freopen("replace.out", "w", stdout);
cin >> n >> q;
FOR(i, 1, n) f1[i] = gethash(S1[i], S2[i], al[i], ar[i], a[i]);
FOR(i, 1, q) f2[i] = gethash(T1[i], T2[i], bl[i], br[i], b[i]);
sort(arr.begin(), arr.end());
arr.erase(unique(arr.begin(), arr.end()), arr.end());
FOR(i, 1, n) if (f1[i]) buc[f1[i]=low(arr.begin(), arr.end(), a[i])-arr.begin()+1][0].eb(i);
FOR(i, 1, q) if (f2[i]) buc[f2[i]=low(arr.begin(), arr.end(), b[i])-arr.begin()+1][1].eb(i);
FOR(i, 1, (int)arr.size()) {
t1.tot = t2.tot = timer = 0;
t1.init(0); t2.init(0);
for (int x : buc[i][0]) {
int p = 0, q = 0;
FOR(j, ar[x]+1, (int)S1[x].size()-1) p = t2.insert(p, S1[x][j]-'a');
REV(j, al[x]-1, 0) q = t1.insert(q, S1[x][j]-'a');
vec[q][0].eb(p, 0);
tmp.eb(q);
}
for (int x : buc[i][1]) {
int p = 0, q = 0;
FOR(j, br[x]+1, (int)T1[x].size()-1) p = t2.insert(p, T1[x][j]-'a');
REV(j, bl[x]-1, 0) q = t1.insert(q, T1[x][j]-'a');
vec[q][1].eb(p, x);
tmp.eb(q);
}
t2.dfs(0); lim = timer; t1.getans(0);
for (int x : tmp) vec[x][0].clear(), vec[x][1].clear();
tmp.clear();
}
FOR(i, 1, q) cout << rst[i] << '\n';
return 0;
}
T4 [CSP-S 2025] 员工招聘 / employ
相比 T3 没想到 trie,T4 没想到容斥更显脑残。
排序的计数问题,硬做显然是值域扫描线+插入/连续段 dp,然后对于复杂限制进行贡献提前或延迟,反推系数。
- 但是我数学不好,不会推系数怎么办?(全还给 jzp 了。)
显然这道题最难做的限制是 \(c_i>k\),那么容斥掉,也即每次钦定若干个 \(i\) 满足 \(c_i\leq k\),在 dp 转移时带上钦定了多少个元素的状态,以 \(-[c_i\leq k]\) 为容斥系数。
那么直接考虑设 \(f_{i,j,k}\) 为值域扫描线考虑了从小到大前 \(i\) 个人,\(j\) 个人被拒绝,被钦定的元素数量为 \(k\),此时的方案数量。
设 \(sum_v\) 为满足 \(c_i\leq v\) 的 \(i\) 的数量,考虑转移:
- 若 \(s_i=0\),直接有 \(f_{i,j,k}\leftarrow f_{i-1,j-1,k}\)。
- 若 \(s_i=1\):
- 若该人被拒绝,则一定有 \(c_i\leq k\),也即必然钦定,有 \(f_{i,j,k}\leftarrow (sum_{j-1}-(k-1))\cdot f_{i-1,j-1,k-1}\)。
- 若该人被接受,则需要同时转移钦定与不钦定进行容斥,有 \(f_{i,j,k}\leftarrow f_{i-1,j,k}+(sum_{j}-(k-1))\cdot f_{i-1,j,k-1}\)。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long i64;
constexpr int N = 505;
constexpr int MOD = 998244353;
#define add(a,b) (a+=b)>=MOD&&(a-=MOD)
int n, m, c[N], cnt[N];
char s[N];
int f[N][N][N], fac[N];
int main() {
freopen("employ.in", "r", stdin);
freopen("employ.out", "w", stdout);
scanf("%d%d%s", &n, &m, s+1);
for (int i = 1; i <= n; i++) {
scanf("%d", &c[i]);
++cnt[c[i]];
}
fac[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = (i64)fac[i-1]*i%MOD;
for (int i = 1; i <= n; i++) cnt[i] += cnt[i-1];
f[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
if (s[i] == '0') {
for (int j = 0; j < i; j++) {
for (int k = 0; k < i; k++)
add(f[i][j+1][k], f[i-1][j][k]);
}
} else {
for (int j = 0; j < i; j++) {
for (int k = 0; k < i; k++) {
add(f[i][j][k], f[i-1][j][k]);
add(f[i][j][k+1], MOD-(i64)(cnt[j]-k)*f[i-1][j][k]%MOD);
add(f[i][j+1][k+1], (i64)(cnt[j]-k)*f[i-1][j][k]%MOD);
}
}
}
}
int rst = 0;
for (int i = 0; i <= n-m; i++) {
for (int j = 0; j <= n; j++) {
add(rst, (i64)f[n][i][j]*fac[n-j]%MOD);
}
}
printf("%d", rst);
return 0;
}

浙公网安备 33010602011771号