CQ 省集记录
Day 1
D1T1 Coreputer
题意:交互题,有隐藏的 01 序列 \(a_{1 \sim 16}\),查询给出集合 \(S\),记 \(f(S) = \sum_{i \in S} a_i\),交互库返回:
- \(f(S) > f(U \setminus S):1\)。
- \(f(S) = f(U \setminus S):0\)。
- \(f(S) < f(U \setminus S):-1\)。
需要在 \(18\) 次查询内求出 \(a_{1 \sim 16}\)。
考虑如果我们知道一个集合 \(S\) 满足 \(f(S)=0\),那么我们可以根据 \(f(S \cup \{i\})\) 的值来确定 \(a_i\)。基于这个想法,我们先花 \(4\) 次操作二分出一个分界点,然后再 \(16\) 次查询查询出来 \(a_{1 \sim 16}\)。
但是这样超了两次,先确定 \(a_{p+1}=1\) 减少一次,再由于知道 \(a_{1 \sim 15}\) 可以直接知道 \(a_{16}\) 的值,还可以凹一次。
::::info[代码]
const i32 N = 1e2 + 5, mod = 998244353;
int query(std::vector<int> S);
i32 pos, posval, a[N];
fn i32 qry(i32 mid) {
vector <int> S;
rep (i, 1, mid) S.pb(i - 1);
return query(S);
}
fn vector <int> cup(vector <int> a, vector <int> b) {
for (i32 x : b) a.pb(x);
return a;
}
vector <int> solve(int n) {
rep (i, 1, n) a[i] = 0;
pos = posval = -100;
i32 l = 1, r = n - 1;
while (l <= r) {
i32 mid = (l + r) >> 1;
i32 res = qry(mid);
// cerr << "query " << mid << " " << res << "\n";
if (res < 1) pos = mid, posval = res, l = mid + 1;
else r = mid - 1;
}
// cerr << pos << " " << posval << "\n";
if (posval == -100) {
vector <int> ans; ans.pb(1);
rep (i, 2, n) ans.pb(0);
return ans;
}
a[pos + 1] = 1;
if (posval == 0) {
vector <int> S;
rep (i, 1, pos) S.pb(i - 1);
rep (i, pos + 2, n) {
if (query(cup(S, {i - 1})) == 1) a[i] = 1;
else a[i] = 0;
}
i32 cnt = 0;
rep (i, pos + 1, n) cnt += a[i];
S.clear();
rep (i, pos + 1, n) S.pb(i - 1);
rep (i, 2, pos) {
if (query(cup(S, {i - 1})) == 1) a[i] = 1;
else a[i] = 0;
}
i32 cnt1 = 0;
rep (i, 2, pos) cnt1 += a[i];
a[1] = (cnt != cnt1);
vector <int> ans;
rep (i, 1, n) ans.pb(a[i]);
return ans;
} else {
vector <int> S;
rep (i, 1, pos) S.pb(i - 1);
rep (i, pos + 2, n) {
if (query(cup(S, {i - 1})) == 1) a[i] = 1;
else a[i] = 0;
}
i32 cnt = 0;
rep (i, pos + 1, n) cnt += a[i];
S.clear();
rep (i, pos + 2, n) S.pb(i - 1);
rep (i, 2, pos) {
if (query(cup(S, {i - 1})) == 1) a[i] = 1;
else a[i] = 0;
}
i32 cnt1 = 0;
rep (i, 2, pos) cnt1 += a[i];
a[1] = (cnt != cnt1 + 1);
vector <int> ans;
rep (i, 1, n) ans.pb(a[i]);
return ans;
}
}
::::
D1T2 Tree
题意:给定一棵大小为 \(n\) 的树 \(T\) 和一个常数 \(m\),定义 \(f(S)\) 为 \(S\) 最小斯坦纳树的最小点覆盖大小,求所有 \(S\) 的 \(f(S)^m\) 之和。
\(1 \le n \le 3 \times 10^5,1 \le m \le 200\)。
套路化地转下降幂,考虑一下 \(\binom{f(S)}{i}\) 的组合意义,也就是钦定 \(i\) 个最小点覆盖中的点。
- 树上最小点覆盖事实上有贪心策略:如果一个点的所有儿子有一个没被覆盖的则覆盖自己,否则不覆盖。
基于此我们设计 dp 状态为 \(f_{i,j,0/1}\) 代表做完了子树 \(i\),钦定了 \(j\) 个点,这个点是否被覆盖。应该要注意这里计数的对象是什么,事实上是对 \(S\) 计数而非对连通块计数,所以我们要考虑每个连通块中的点是否需要选入 \(S\) 集合。
- 如果节点 \(i\) 为根,并且节点 \(i\) 有 \(\ge 2\) 个儿子,那么 \(i\) 可选可不选;否则 \(i\) 必须选。
考虑到实现,我们拿不考虑此限制的方案数,减掉只有 \(1\) 个儿子的方案数即可,复杂度为树形背包 \(O(nm)\)。
::::info[代码]
vi G[N];
i32 n, m, u, v, f[N][M][2], g[M][2], h[M][2], siz[N], ans[M];
fv inc(i32 &x, i32 y) {
x = (x + y >= mod ? x + y - mod : x + y);
}
fv dec(i32 &x, i32 y) {
x = (x - y < 0 ? x - y + mod : x - y);
}
fv dfs(i32 u, i32 fa) {
siz[u] = 0;
for (i32 v : G[u]) if (v != fa) dfs(v, u);
rep (i, 0, m) for (i32 o : {0, 1}) g[i][o] = 0;
g[0][1] = 1;
for (i32 v : G[u]) {
if (v == fa) continue;
rep (i, 0, m) h[i][0] = h[i][1] = 0;
rep (i, 0, min(siz[u], m)) {
rep (j, 0, min(siz[v], m - i)) {
rep (a, 0, 1) rep (b, 0, 1) inc(h[i + j][min(a, b)], g[i][a] * f[v][j][b] % mod);
}
}
siz[u] += siz[v];
rep (i, 0, m) for (i32 o : {0, 1}) inc(g[i][o], h[i][o]);
}
rep (i, 0, m) for (i32 o : {0, 1}) g[i][o] = g[i][o] * 2 % mod;
siz[u]++;
rep (i, 0, m) {
f[u][i][0] = g[i][1], f[u][i][1] = g[i][0];
if (i) inc(f[u][i][1], g[i - 1][0]);
}
dec(f[u][0][0], 1);
rep (i, 0, min(siz[u], m)) inc(ans[i], (f[u][i][0] + f[u][i][1]) % mod);
for (i32 v : G[u]) {
if (v == fa) continue;
rep (i, 0, min(m, siz[v])) {
dec(ans[i], f[v][i][0]), dec(ans[i], f[v][i][1]);
dec(ans[i + 1], f[v][i][0]);
}
}
}
fv sol() {
cin >> n >> m;
rep (i, 1, n) G[i].clear();
rep (i, 1, n - 1) cin >> u >> v, G[u].pb(v), G[v].pb(u);
rep (i, 0, m) ans[i] = 0;
dfs(1, 0);
i32 sum = 0;
rep (i, 0, m) inc(sum, fac[i] * S[m][i] % mod * ans[i] % mod);
cout << sum << "\n";
}
::::
D1T3 Weight
题意: https://qoj.ac/problem/15436/statement/zh_cn 。
我们把 \((|S|,|N(S)|)\) 看成平面上的一个点,首先有用的点只有 \(n\) 个,并且这 \(n\) 个点横纵坐标单增。因为每个 \(|S|\) 只有 \(|N(S)|\) 最大的一个会被启用,不过这里暂时不提如何求最大的一个。
- 存在一种方案,选择的集合不会超过 \(2\) 个。
考虑三个点 \((x_{1 \sim 3},y_{1 \sim 3})(x_i \le x_{i+1})\),如果 \((x_2,y_2)\) 在 \((x_1,y_1),(x_3,y_3)\) 的连线上方,把它的权值分给两边的点一定不劣;同理如果 \((x_2,y_2)\) 在 \((x_1,y_1),(x_3,y_3)\) 连线下方,则把两边的权值分给它一定不劣。
基于这个观察,我们可以说明有用的点必须是下凸包上面的点,并且可以将下凸包上的点的权值重分配到相邻的两个点。
考虑如何去求这个凸包。首先 \((0,0)\) 和 \((n,n)\) 都是合法的点,考虑分治求两个点 \(L \to R\) 间的凸包,具体来说,我们斜率为 \(L\) 到 \(R\) 斜率的直线去切所有点,切出来的点一定在下凸包上面,不妨设是 \(M\),如果 \((M \ne L \wedge M \ne R)\),我们继续递归处理 \(L \to M, M \to R\)。
现在的问题是,如何去用一条斜率为 \(k\)(\(k\) 是分数)的直线去切点集,那么截距离就是 \(|N(S)|-k|S|\),要最小化截距,也就是最大化 \(k|S| - |N(S)|\)。考虑对其网络流建模:
- 选择一个左部点,贡献为 \(+k\),需要强制选所有相邻的右部点。
- 选择一个右部点,贡献为 \(-1\)。
这实际上就是最大权闭合子图模型,我们建模 \(S \to i\),容量为 \(k\),\(i+n \to T\),容量为 \(1\),\(u \to v\),容量为无穷,跑最小割即可求出对应的 \((|S|,|N(S)|)\)。
我们已经求出来了下凸包,考虑如何求答案,则有约束:
- \(p_1 + p_2 \le 1\)。
- \((ax_1 + y_1)p_1 + (ax_2 + y_2)p_2 \le k\)。
所求即为 \(a(x_1p_1 + x_2p_2)\) 的最大值,最大值在前两条限制的交点取到。
分析复杂度,凸包上的点是 \(O(n^{\frac{2}{3}})\) 级别的,时间复杂度 \(O(n^{\frac{2}{3}}) \text{flow}(n,m)\),卡不满所以足以通过。暂时没写。
非传统题选讲 刘家炜
QOJ1139
努力的方向是去维护子树信息,想要去维护 \(t\) 位于 \(s\) 哪个子树,或者父亲方向。
最直观的想法是按 DFS 序标号,但是 DFS 序标号会有漏洞,最后一个子树我们并不知道右端点是多少。我们发现维护子树信息并没有很好利用父亲和自身的编号,考虑以某种方式重编号,同时还要保持子树内的编号连续。
我们对树二分图染色,按如下方式编号:
- 如果是黑点,则先给子树内的点编号,最后给自己编号。
- 如果是白点,则先给自己编号,然后再给子树内的所有点标号。
QOJ8650
我们考虑随便挑一个点为根,并且以这个点为根查询 \(n\) 次求出到所有点的优先级。
现在有一个点 \(u\),考虑不停去查询离它优先级 \(1 \sim i\) 的点,直到查询出来 \(a_i\) 使得 \(1\) 对 \(a_i\) 的优先级比 \(1\) 对 \(u\) 更高,那这个 \(a_i\) 就是 \(u\) 的父亲。
这样看起来是 \(3n\) 次操作,不过分析一下发现在查询到父亲之前的查询,都可以查出来一个儿子,所以是均摊 \(2n\) 次操作。
QOJ13902
首先观察一下 \(n=3\) 咋做,第一次查询肯定查询 \(p_0-1\),得到了 \(p_1\) 或者 \(p_1+p_2\) 的值。
前者是好做的,考虑后者如何才能避免再次购买到 \(p_1\);事实上,考虑用 \(p_1+p_2\) 的值除以二再次进行查询即可避免买到 \(p_1\) 的同时买到 \(p_2\)。
考虑类似地进行扩展,记我们一次查询出来 \(c = \sum_{i \in [1,k]} a_{p_i}\) 的值,那么 \(\min(p_i)\) 的值肯定大于 \(\left \lfloor \frac{c-1}{k} \right \rfloor\)。我们拿 \(\left \lfloor \frac{c-1}{k} \right \rfloor\) 去进行购买,则会递归到一个后缀的子问题。
不妨设可以解决这个子问题,我们给刚刚的 \(c\) 减去所有后缀中的数,重新递归去做类似的子问题即可。分析操作次数,每一个递归的子问题都最多会对后缀每个数购买一次,所以是对的。
QOJ16001
首先随机一个满秩的序列是 \(\prod (1 - \frac{1}{2^i})\) 收敛于 \(\frac{1}{4}\) 的,也就是期望随机 \(4\) 次可以随机出来一个满秩的序列。
考虑去删掉一个数递归子问题;设删掉 \(C_n\),求剩余元素的子空间。维护空间的一组基,初始的基为 \((1,2,...,2^{n-1})\)。
对基底中每个元素 \(x\),询问 \(C_n:=C_n \oplus x\) 的秩是否发生变化;记不发生变化的为 \(x'_{1 \sim m}\),变的为 \(x''_{1 \sim n - m}\)。
- 若 \(a,b\) 都不在子空间中,则 \(a \oplus b\) 一定在子空间中。
根据这个结论,那么 \((x_{1 \sim m},x''_2 \oplus x''_1,x''_3 \oplus x''_1,...,x''_{n-m} \oplus x''_1)\) 为 \(C_{1 \sim n-1}\) 的一组基,也就可以递归子问题。
假设已经求出来了 \(a_{1 \sim n-1}\),现在要解 \(a_n\),当前的基底为 \(s_{1 \sim n}\),我们需要依次判断 \(a_i\) 在每个基上面是否有分量。用 \(s_{1 \sim n-1},C_n\) 查询即可判断 \(s_n\) 是否在 \(C_n\) 上有分量,如果有,则 \(C_n \to C_n \oplus s_n\),然后类似递归做下去即可。
QOJ14330
建出来表达式树,考虑只把 \(x_1\) 和 \(x_k\) 设为 \(1\),其他都为 \(0\)。答案为 \(1\) 当且仅当 \(x_k\) 向上只经过一次右儿子。根据这个,我们可以求出来左链的右子树大小,递归处理可以做到 \(O(n^2)\) 次。
改一下表达式树,将一个括号内若干个并列的减号改为多叉树。当且仅当第一个儿子为 \(1\),后面都为 \(0\),子树会向上返回 \(1\)。
考虑从小到大去加点,假设现在已经加入了 \(1 \sim i\) 的点,并且当前点 \(i\) 在这个多叉树上根到 \(i\) 的链为 \(u_{1 \sim k}\)。考虑 \(u_{1 \sim k}\) 一定都会有第一个儿子并且已经在前面求过了,这里我们将它们全部设为 \(1\),其他设为 \(0\),考虑 \(a_{i+1} :=1\),\(i+1\) 一定为 \(u_{1 \sim k}\) 的某个点的某个子节点的第一个儿子:
- 如果是 \(u_{k}\) 的某个子节点的第一个儿子,则表达式的值会反转。
- 如果是 \(u_{k-1}\) 的某个子节点的第一个儿子,则表达式的值会不会反转。
- 如果是 \(u_{k-2}\) 的某个子节点的第一个儿子,则表达式的值会反转。
也就是说,这个是否会反转和向上跳的祖先的个数的奇偶性相关。如果值没变,则可以断定不是 \(u_k\) 的子节点的第一个儿子,直接向上退出递归;否则,将 \(u_k\) 和 \(u_{k-1}\) 的第一个点改为 \(0\),再查询一遍,来确定是否位于 \(u_k\) 的某个子节点的第一个儿子。
这样做的操作次数是 \(3n\) 次,超限了 \(2.5n\) 次。考虑退栈的过程,如果值没变,则下一次查询 \(u_{k-1}\) 的结果我们已经知道;否则可以直接向上跳两层跳到 \(u_{k-2}\),因此总操作次数可以优化到 \(2.5n\) 次。
Day 2
D2T1 排列游戏
太难了。
D2T2 排序大师
我们转化一下题意,发现每个连通块之间可以任意换,也就是我们需要判断每个连通块排序之后是否有序。
也就是说,我们只需要对每个连通块求内部选择的方案即可。我们钦定一个连通块 \(\mathcal{C}\) 中有 \(|\mathcal{C}|-i\) 个数强制不选,容斥后有 OGF 为:
我们注意到 \(\frac{1}{1-ix}\) 的分母是 \(i\) 次多项式,而分子不超过分母,根据调和级数,分子分母的次数应该都是 \(O(n \log n)\) 级别的。
暴力维护分式的复杂度是 \(O(n^3 \log^2 n)\) 的,对分子 Lagrange 插值维护 \(O(n \log n)\) 项点值,设 \(f_{i,j}\) 代表考虑 \(i\) 的子树,当前连通块大小为 \(j\) 的权值和,对这个东西跑 \(O(n \log n)\) 次,最后展开分式,总复杂度 \(O(n^3 \log n + n^2 \log^2 n + nm \log n)\)。
D2T3 吃不饱堡
考虑从 \(i \to j\),考察了动能定理,会选动能较小的一个作为初速度,具体来说:
- 如果 \(v_i^2 + 2h_i \le l_j^2 + 2h_j\),选择 \(v_i\) 作为初速度,时间为 \(-v_i+\sqrt{v_i^2 + 2(h_i-h_j)}\)。
- 否则,选择 \(l_j\) 作为末速度,时间为 \(l_j - \sqrt{l_j^2 - 2(h_i-h_j)}\)。
我们设 \(f_i\) 进行 dp,问题在于如何转移值。这里的一个 trick 是,李超树是可以维护非一次函数的!!!
- 对于第一类转移,令 \(x=-2h_j\),但是还需要满足 \(v_i^2 + 2h_i \le l_j^2 + 2h_j\) 的限制,这又是一维偏序,直接做是 \(O(n \log^3 n)\) 的。思考一下 \(x\) 的定义域是 \(v_i^2 + 2h_i - x \ge 0\),但是 \(h_i \ge h_j\),也就是说必定合法,改为全局插入即可做到 \(O(n \log^2 n)\)。
- 对于第二类转移,令 \(x=l_j^2 + 2h_j\),则 \(x\) 的定义域为 \([2h_i,v_i^2 + 2h_i]\),区间插入函数即可做到 \(O(n \log^2 n)\)。
组合计数选讲
CMO2025 P3
概括:
求出 \(F(1 \sim m),m \le 10^6\)。
考虑 \(\binom{i+j}{i}2^{-i-j-1}\) 的组合意义为,随机一个长度为 \(i+j+1\) 的 \(\texttt{01}\) 串,第 \(i+1\) 个 \(\texttt{1}\) 恰好出现在 \(i+j+1\) 位置的概率。
考虑对组合意义加上 \(j\) 的求和,组合意义变成:随机生成 \(\texttt{01}\) 串,前 \(i+n\) 项至少出现 \(i+1\) 个 \(\texttt{1}\) 的方案数。
观察到在 \(n=i+1\) 时,这个概率恰好等于 \(\frac{1}{2}\),所以我们可以计算 \(j \le i\) 和 \(j \ge i\),最后删掉 \(j=i\),也就是 \(F(n) = n - \sum \binom{2i}{i}2^{-2i-1}\)。后面的部分不是 OI 相关。
QOJ16229 Grid Path Tree
概括:出题人给你一个式子:
请你求 \(ans_{1 \sim n+m}\);\(n,m \le 5 \times 10^6\)。
神秘。用范德蒙德卷积拆出前两个组合数的组合意义:
考虑组合意义:将两个长度为 \(n,m\) 的序列黑白染色,然后各自划分成三段,要求两个序列的第一段黑色数量相等,最后一段黑色数量相等,中间的段数量分别是 \(x\) 和 \(x-1\)。
这个所谓划分感觉不好去计数,我们对其进行转化:将划分的格子看作黑色的格子,那么组合意义变为:给两个长度为 \(n+2,m+2\) 的序列黑白染色,选择 \(s,t\),在两个序列第 \(s\) 个,倒数第 \(t\) 个黑色处断开,中间分别有 \(x,x-1\) 个黑色。
转化成了这个形式,我们发现可以直接算答案:如果两个序列有 \(y,y-1\) 个黑色,那么给 \(x\) 贡献的答案就是 \(y-x-1\),写个前缀和就做完了。
我们思考这一大坨叽里咕噜的组合意义到底在干什么,讲题人讲,这么一大坨叽里咕噜本身其实就是在做一些组合恒等式和和式顺序交换,只是写成这种形式不容易导致瞎几把推式子炸掉而已。
QOJ5121 Square Grid
考虑旋转坐标轴 \(45^{\circ}\),每次操作变为横着走半步再竖着走半步,这样可以让 \(x,y\) 轴独立。
答案就是 \([x^{(t+a)/2}](1+x)^t \bmod (x^m - 1) \times [x^{(t+b)/2}](1+x)^t \bmod (x^m - 1)\),可以把多项式预处理。
QOJ14419 Maxinum Segment Sum
考虑维护每个位置的最大后缀和,最大后缀和是 \(s \to \max(0,s+a_i)\)。
加入时间轴并看作是在网格上走路,等于说每次向右上或者向右下(不穿过边界),不能碰到 \(y=1,2,...,n\) 的方案。为了解决掉下边界,每次碰到 \(y=0\) 继续向下走的时候,我们认为它继续往下走,将坐标轴关于 \(y=-0.5\) 对称,就可以做反射容斥了。
UOJ981 决战中途岛
写出走路的生成函数,是 \((x+x_{-1})(x+y+x^{-1}+y^{-1})\),因式分解得到 \(\frac{(x^2+1)(x+y)(xy+1)}{x^2y}\)。考虑给网格转 \(45^{\circ}\),令 \(u = xy,v=x/y\),生成函数为 \((1+u)(1+v)(1+1/uv)\)。
什么东西会有三个正方向呢?三角形网格有这个性质。把问题放到三角形网格上面去看,也就是从 \((0,0)\) 出发,移动区域被限制在一个 \(120^{\circ}\) 的扇形,移动到某个点的答案。
反射容斥,由于这是三角形网格,我们会对应出来 \(6\) 个初始点。然后考虑计算单个点无限制如何算方案,事实上枚举某个坐标轴的移动步数,另外两条轴解方程可以得到。
如何理解反射容斥?
没听懂,设计了一个二元函数 \(Q(x,y)\),无限制的移动是 \(Q(x,y)(x+y)=Q(x,y)\) 也就是 \(Q(x,y)(x+y-1)=0\),然后反射容斥引入一个函数 \(G\),变为 \(Q(x,y)(x+y-1)=G\),然后不懂了,摆了。
Day 3
D3T1 Remove
题意:给你一个串 \(S\),你需要构造若干个回文串拼在一起,每次可以选择两个相邻相同的数删掉,若干次操作后需要变成 \(S\),求最小要多少个回文串,给出方案。 \(|S| \le 500\)。
省流:答案五分钟,构造三小时,区完了。
首先一个明显的观察是,对初始串 \(S\),如果其中有两个相邻的位置 \(x,y\) 并且 \(S_x,S_y\) 相等,那么我们可以直接删掉这两个位置去做构造,不过这个操作不一定不劣。把匹配看成括号,会发现这其实是在说一对左右括号可以删掉。归纳下去可以得到,一个合法的括号序列可以被删掉。
我们考虑一件事情,我们尝试去把 \((\# *)\) 这种结构两边的括号给匹配掉,我们发现是容易的,
过程大概如下:\(\# \to (\#),* \to (*),\# * \to (\#)(*) \to (\# *)\)。基于这个观察,设计区间 dp,\(f_{l,r}\) 代表区间 \([l,r]\) 的答案,有转移:
- 若 \(s_l=s_r:f_{l+1,r-1} \to f_{l,r}\)。
- \(f_{l,k}+f_{k+1,r} \to f_{l,r}\)。
答案即为 \(f_{1,n}\),好了,开始写代码,我们考虑如何写这个回文串,会发现这个是简单的。代码难点在于最开始就消掉的相邻的括号,我写写写写写写写写写,然后做完了。
D3T2 Addition
暂时没补,先跳一下。
D3T3 Maxmex
题意:初始有空集合 \(S_{1 \sim n}\),每次操作向区间添加 \(x\),或者查询区间 \(\text{mex}(S_i)\) 的最大值,强制在线。\(n,m \le 5 \times 10^5\)。
太神奇了,以为是重工业二维平面题,结果是小清新技巧题。
我们建立一棵线段树,对每个节点维护哪些数没有被选入其中。初始只有根节点有 \(1 \sim n\)。对于每个节点,不在它集合里面的值就是它到根的所有节点集合的并。
考虑这个怎么维护 \(\text{mex}\) 的最大值,每个点的答案为左右儿子两个点的答案的最大值,再和自身集合中最小的数取 \(\min\)。
考虑每次给区间加入一个数,就等于说从集合中取出一个数下放,然后做完了,时间复杂度 \(O(n \log^2 n)\)。
构造题选讲 何宇翔
CF1646F
比较简单,找到中间态,中间态是每个人拿到 \(1 \sim n\) 的卡各一张。
QOJ10429
懒得写了。
后面没听。

浙公网安备 33010602011771号