2022 正睿 NOIP 十连测
Day1
100+100+70+60,rank 5。
A
对 \(\{a_n\}\) 排序后,最优的方案一定是选一个前缀,于是二分一下即可。
B
钦定 \(1\) 为根,对于一条非树边 \((u_i,v_i)\),在树上 \(u_i\leadsto v_i\) 的路径上的所有边的边权都需要小于这条非树边的边权。根据这个,我们可以按照边的编号从小到大贪心。用并查集维护某个点 \(u\) 的祖先中,深度最小的、还未确定边权的 \((x,\mathrm{fa}_x)\) 即可。
时间复杂度 \(O(n\log n)\)。
C
因为写完这题以后没时间对拍了,所以写挂了。
设 \(f_i\) 表示:只考虑所有 \(r_j\le i\) 的区间 \([l_j,r_j]\),让这些区间形成的连通块大小不超过 \(k\) 的最小代价。枚举一个 \(x\le i\),我们计算使得 \([x,i]\) 中的区间形成一个连通块的代价 \(\operatorname{cost}(x,i)\),转移就是 \(f_i=\min_{1\le x\le i}\{f_{x-1}+\operatorname{cost}(x,i)\}\)。这个代价分为两部分:
- 设完全被 \([x,i]\) 包含的区间有 \(c\) 个,若 \(c>k\),我们需要删除其中代价最小的 \((c-k)\) 个区间;
- 我们需要将 \(l_j\in [1,x)\land r_j\in [x,i]\) 的区间全部删除。
通过一些简单的处理,可以做到 \(O(n^2\log n)\)。
D
洛谷 P6106 弱化版。
我的 60 分做法
对于测试点 \(1,2,3,4,7,8\),暴力拆出来的矩形个数 \(c\) 不超过 \(2.5\times 10^8\)。也就是说,我们需要支持 \(c\) 次矩形加,加完以后有 \(q\) 次单点查询。用分块平衡一下,可以做到 \(O(c+q\sqrt{V})\)。
但是直接这样做会爆空间。用莫队二次离线中的优化空间的方法,我们只记录线段从哪里开始移动、移动了多少个位置、移动的方向,在扫描线的过程中用 std::forward_list
维护所有存在的线段。这样我们只需要记录 \(O(m)\) 个操作,空间复杂度 \(O(n+m+q)\)。
正解
对于正着的移动和斜着的移动分别考虑。正着的、斜着的移动分别有两种,我们分别只考虑其中的一种,剩下的一种通过旋转坐标轴来处理。仍然做扫描线并维护差分数组。
- 对于竖向的移动,在差分数组上表现为区间加。
- 对于斜向的移动,斜着做扫描线。
代码不可能写。
Day2
100+100+10+30,rank 11。
A
因为 \(a_i\) 和 \(x\) 总共不超过 \(10\) 种数,可以对于 \(2^{10}\) 种选择的方案都预处理出其背包,查询时直接使用。时间复杂度 \(O(2^cm+n+q)\),其中 \(c=10\)。
B
直接拉格朗日插值即可。
C
写错了一行,然后 100->10。能不能给个大样例啊?
对于一条边 \(\{u,v\}\),设其颜色集合是 \(S_{\{u,v\}}\)。如果 \(|S_{\{u,v\}}|\ge 3\),我们其实不关心其中具体有什么颜色。
点分治,每次处理经过分治中心 \(u\) 的询问。对于与 \(u\) 直接相连且没被当作分治中心的点 \(v\),我们用一遍 DP 算出其子树内的 \(f_{x,0/1}(x\in \operatorname{subtree}(v))\),表示当 \(\{u,v\}\) 取第 \(0/1\) 种颜色时,\(u\leadsto v\) 的路径的权值最大值。处理询问是简单的。
时间复杂度 \(O(n\log n)\)。
D
设 \(f_{i,x}\) 表示以 \(i\) 为根的子树内,\(A_i=x\) 时的 \(\sum_u w_uA_u\) 最大值。转移是:
可以看出 \(f_u\) 是由连续的斜率单调递减的一次函数组成的。考虑 slope trick,维护 \(f_u\) 的斜率最小值 \(l_u\) 和最大值 \(r_u\),以及它的所有斜率转折点。对于那个 \(\max\),相当于将 \(f_v\) 沿 \(x=z_{u,v}/2\) 对称后去掉所有斜率 \(>0\) 的部分。
最后一个问题是计算答案。用归纳法可以证明 \(\forall u,l_u\le 0\le r_u\)。所以 \(f_u\) 一定存在一段斜率 \(=0\) 的部分。考虑递归计算答案,设当前要计算 \(A_u\) 最优是什么,且它的父节点给它一个限制 \(A_u\le z\)。设 \(f_u\) 斜率 \(=0\) 的一段的开始位置在 \(L_u\),那么最优一定取 \(A_u=\min(z,L_u)\),然后递归下去即可。
Day3
100+100+60+30,Rank 25。
A
略。
B
设 \(f_{u,k}\)(\(k\in \{0,1\}\))表示仅考虑以点 \(u\) 为根的子树,且 \((u,\mathrm{fa}_u)\) 间的边是否 \(\le a_u\),在此情况下边权和的最大值。
转移时,我们需要选择 \(\lfloor\frac{\mathrm{deg}_u}{2}\rfloor+1-k\) 条 \(u\) 连向某个儿子的边,并使得这些边的边权 \(\le a_u\)。用堆维护这个即可,时间复杂度 \(O(n\log n)\)。
C
随机选择一个 \(a_i\),它被包含在答案里的概率至少是 \(\frac{1}{2}\)。考虑 \(a_i\) 的每个因数 \(d\),设 \(f(d)=\sum_{j=1}^n [d\mid a_j]\),答案就是最大的 \(d\) 使得 \(f(d)\ge \lceil \frac{n}{2}\rceil\)。而这些 \(f(d)\) 都是可以用高维后缀和快速算出的,因此总的时间复杂度就是 \(O((n\log a_i+\operatorname{d}(a_i)\omega(a_i))\log n)\)。
D
Day4
100+100+45+0,Rank 23。
A
考虑第一个 \(1\) 所在的位置 \(k\),那么 \(k\) 后面的都不会对答案造成贡献。而 \(k\) 前面的排成 \(a_1^{a_2a_3\dots a_{k-1}}\) 是最优的。
B
将所有已知的边按边权从小到大排序,并求出最小生成树。对于每条已知的非树边,求出它连接的两个点会在哪条树边加入后连通。
那么我们就可以算出边权相邻的两条已知的边之间能放多少条未知的边,然后检查总的能放的个数是否等于未知边条数即可。时间复杂度 \(O((n+m)\log n)\)。
C
以后一定补!
D
Day5
100+100+20+30,Rank 32。
A
从前往后扫,维护一个栈表示当前合并完得到的元素。每次加入一个元素时,检查它与栈顶能不能合并,最终若栈中只剩一个元素,那么答案为 Yes
。用 std::bitset
维护每个数中的质因子,时间复杂度 \(O(\frac{n\pi(V)}{w})\)。
B
每行的 LR
,以及每列的 UD
,都是独立的,不妨只考虑一行的 LR
如何计算答案。我们要算出这一行匹配的方案数 \(c\) 以及所有匹配的喜悦值之和 \(s\)。
设 \(f_{i,j}\) 表示考虑前 \(i\) 个位置,其中有 \(j\) 个 R
想匹配但还没匹配的喜悦值之和,\(g_{i,j}\) 表示这样的方案数。转移是简单的,而 \(c=g_{n,0},s=f_{n,0}\)。
设 \(M=\prod c\),答案就是 \(\sum_{i} \frac{Ms_i}{c_i}\)。时间复杂度 \(O(n^3)\)。
C
首先是一个经典的容斥,答案是:
考虑将二项式系数拆成单项式之和,将式子化为:
\(\sum_{i=0}^n f(i)i^l\) 可以数位 DP:设 \(F_{i,lim,j}\) 表示当前考虑第 \(i\sim \lceil\lg n\rceil\) 位,是否卡边界,\(l=j\) 的答案。转移时,考虑第 \(i\) 位填了 \(x\),用二项式定理将式子拆开转移即可。
时间复杂度 \(O(k^3 \log x)\)。
Day6
100+100+60+40,Rank 16。
A
“移动到相邻的格子”和“跳一步或多步”这两种操作间是互不影响的,也不会走到重复的格子。移动到相邻的格子是好做的,对于“跳”操作,预先将所有能够跳一步到达的点对之间连边,并预处理所有连通块的大小即可。
时间复杂度 \(O(n\log n)\)。
B
首先,分三种情况讨论:
- 若 \(X\) 中既没有
G
也没有T
,则最优方案是执行 \(\theta(n,n)\); - 若 \(X\) 中有
G
但没有T
,那么找到第一个G
的位置,设为 \(p\); - 若 \(X\) 中有
T
,那么找到第一个非A
的位置,设为 \(p\)。
若是后两种情况,最优方案就是执行 \(\theta(p,r)\),其中 \(r\) 尚未确定。那么我们要做的就是比较两个方案 \(r_1\) 和 \(r_2\) 的优劣,用字符串哈希求出这两个方案的 LCP,然后比较下一个字符即可。时间复杂度 \(O(n\log n)\)。
C
首先考虑 \(n\le 12\) 怎么做。将这 \(n\) 个点的颜色设为一个数列 \(\{c_n\}\),并搜索出所有可能的 \(\{c_n\}\) 的最小表示。当搜索到一种最小表示时,假如其中有 \(i\) 种数,那么其代表的 \(\{c_n\}\) 的个数就是 \(k^{\underline{i}}\)。这样做的时间复杂度是 \(O(m\operatorname{Bell}(n))\)。
再考虑树怎么做。一个朴素的想法是设 \(f_{u,i}\) 表示考虑 \(u\) 的子树,且 \(c_u=i\) 的方案数。但注意到所有颜色都是等价的,也就是说 \(\forall i,j\in [1,k],f_{u,i}=f_{u,j}\)。所以只需要记录一个 \(f_u\) 即可,时间复杂度 \(O(n)\)。
将上面两个东西合起来。任取原图的一棵 DFS 树,非树边只有 \(m-n+1\) 条,且这些非树边都是返祖边。搜索出所有非树边的上端点的颜色的最小表示,假如其中有 \(cur\) 种颜色,那么我们在树上做一遍 DP:设 \(f_{u,i}\)(\(0\le i\le cur\))表示仅考虑点 \(u\) 的子树以及子树内连出去的返祖边,且 \(c_u\) 在第 \(i\) 个等价类中的方案数。若 \(i=0\),则代表 \(c_u\) 不与任何搜索到的颜色相同,此时 \(c_u\) 有 \(k-cur\) 种颜色可以选择。
设 \(s=\min(n-1,m-n+1)\),那么这个做法的时间复杂度是 \(O((ns+m)\operatorname{Bell}(s))\)。
D
JOISC 铁道旅行。
这个题的连边满足:若 \(L<R\) 且 \(L,R\) 间有边,那么对于任意的 \(x\in (L,R)\cap \mathbb{Z}\),与 \(x\) 有边相连的点都在 \([L,R]\) 中。
所以要想跳得很远,要不然跳得尽量往左,要不然跳得尽量往右。设 \(l_{i,j},r_{i,j}\) 分别表示从点 \(i\) 开始,用不超过 \(2^j\) 次,能够跳到的最左、最右的位置。
考虑一个询问 \((u,v)\),我们从点 \(u\) 开始跳,并记录当前最左、最右能跳到的位置 \(L,R\)。跳的过程中需要满足 \(R<v\)。这样我们找到了 \(u\) 能往右跳到的最大位置 \(R\),此时从 \(v\) 开始往左跳,跳的过程中需要满足 \(L'>R\)。跳完以后 \(u\) 和 \(v\) 只需要一步就能互相到达了。
时间复杂度 \(O((n+q)\log n)\)。
int n, mth[N], l[N][LogN], r[N][LogN]; char s[N];
int main() {
cin >> (s + 1); n = strlen(s + 1);
stack<int> stk;
For(i, 1, n) {
if (s[i] == '(') stk.push(i);
else {
int j = stk.top(); stk.pop();
mth[j] = i, mth[i] = j;
}
}
For(i, 1, n) {
l[i][0] = min(i, mth[i]), r[i][0] = max(i, mth[i]);
if (i > 1) l[i][0] = min(l[i][0], i - 1);
if (i < n) r[i][0] = max(r[i][0], i + 1);
}
For(j, 1, LogN - 1) For(i, 1, n) {
l[i][j] = min(l[l[i][j - 1]][j - 1], l[r[i][j - 1]][j - 1]);
r[i][j] = max(r[l[i][j - 1]][j - 1], r[r[i][j - 1]][j - 1]);
}
int q; cin >> q;
For(_, 1, q) {
int u, v; cin >> u >> v;
if (u == v) { cout << "0\n"; continue; }
if (u > v) swap(u, v);
int L = u, R = u, ans = 0;
Dec(i, LogN - 1, 0) {
int j = max(r[L][i], r[R][i]);
if (j < v) L = min(l[L][i], l[R][i]), R = j, ans += 1 << i;
}
u = R, L = R = v;
Dec(i, LogN - 1, 0) {
int j = min(l[L][i], l[R][i]);
if (j > u) R = max(r[L][i], r[R][i]), L = j, ans += 1 << i;
}
cout << ans + 1 << '\n';
}
return 0;
}