杭州集训记
杭州集训记
信友队
5.28
省流:与 T1 鏖战 5 个小时(1 小时打表找规律 + 2 小时写代码 + 1 小时调试 + 1 小时改进速度),写了一个时间复杂度不正确,代码难度巨大,常数巨高的分块做法,本地跑大样例要 40+s(时限 3s),但是居然过了😰
T2 和 T3 赛时没时间看。
100 + 0 + 0,rk 1/3。
A. 长门有希的序列
C. 小D砍树
改自 CodeChef Chef Cuts Tree。仅有模数不同。
牛逼题。先看一道思路类似的题:
P10632 Normal 给定一棵树,有变量 \(x\),初始为 \(0\)。重复以下过程:
- \(x\) 加上树的大小;
- 如果树不止一个点,从树上等概率随机选择一个点,删去它,递归进入剩下的子树。
求 \(x\) 的期望。对 \(998244353\) 取模。
这个过程实际上就是在点分治中随机选择点,而不是选择重心。它不好直接刻画,我们第一步要做的是转化贡献,也就是考虑对于某个特定的选点过程,重写 \(x\) 的式子。
考虑这个过程产生的点分树,我们宣称:
也就是如果 \(u\) 和 \(v\) 在点分树上有祖先关系,则产生 \(1\) 的贡献。这是容易理解的:选点选到 \(u\) 时,点分树中 \(u\) 的子树中每个点都产生 \(1\) 的贡献,转化一下就得到了上式。
又注意到点分树中 \(u\) 是 \(v\) 的祖先,当且仅当原图 \(u\) 到 \(v\) 的链中 \(u\) 是最早被选的点。设 \(d(u, v)\) 表示 \(u\) 到 \(v\) 简单路径上的点数,则
据此可以导出 \(O(n^2)\) 的做法。
考虑优化。肯定不能枚举 \(u, v\),注意到我们只关心一个点对的距离,所以考虑对距离为 \(d\) 的点对计数(\(d = 1, 2, \cdots, n\))。设 \(cnt_{d}\) 表示长度为 \(d\) 的链数量,则
(长度 \(> 1\) 的链两端都可能作为祖先,统计时记得乘 \(2\))。
那么问题的关键在于求出 \(cnt\)。统计树上路径,肯定要想到点分治。如果 \(d\) 是某个定值,那就是点分治板子。这里用卷积来进行多个 \(d\) 的计数。
设当前的分治中心为 \(u\),考虑求出经过 \(u\) 的长度为 \(1, 2, \cdots, n\) 的链数。用类似树上背包的过程,每次新加入一棵子树时,统计某一端在这棵子树内的链的数量,然后把这棵子树合并到之前的连通块上。设当前计算到第 \(i\) 棵子树,前 \((i - 1)\) 棵子树中的链数为 \(f_{1..n}\),以 \(u\) 为某一端的链数为 \(g_{1..n}\),第 \(i\) 棵子树中的链数为 \(h_{1..n}\),有转移
第一个转移是卷积的形式,用 NTT 优化,第二个直接相加。显然时间复杂度瓶颈在 NTT,仔细分析一下:
设 \(ht_i\) 表示第 \(i\) 棵子树的高度。为了优化,\(h\) 的长度只用设为 \(ht_i\),而 \(f\) 和 \(g\) 的长度不超过前 \(i\) 棵子树的 \(ht\) 取 \(\max\) 乘 \(2\)。显然把子节点按 \(ht\) 排序更优。这样每次 NTT 的长度为 \(O(ht_i)\),总时间复杂度为
由于 \(ht_{i} \le sz_{i}\),所以也可以写成
由于子树 \(sz\) 的和就是 \(sz_{u}\),所以上式不超过 \(O(sz_u \log sz_u)\)。(好吧其实我不太会证明这种带渐进符号的东西。)而每次分治的 \(sz_u\) 之和为 \(O(n \log n)\),所以总时间复杂度为 \(O(n \log^{2} n)\)。
然后看模拟赛题。还是考虑把贡献拆到点对上。可以发现一个图的权值为连通的有序点对数量。设 \(d(u, v)\) 表示 \(u\),\(v\) 之间简单路径的边数,则随机删除 \(k\) 条边以后 \(u, v\) 连通的概率为
暴力做是 \(O(n^3)\)。考虑优化,显然还是把相同的 \(d\) 放到一起算,用上一题的套路求出 \(cnt_{d}\),那么
发现式子中有一项同时有 \(k\) 和 \(d\),不能直接统计。我想了很久代数方法把两个变量分离,但都不可行。实际上还是卷积。先再简化一下式子:
记 \(f_{k} = cnt_{k}(n - 1 - k)!\),\(g_{k} = (n - 1 - k)!\),则
发现这就是差卷积的形式。NTT 即可。
然后这道题的原题模数不能直接 NTT,需要三模 NTT/拆系数 FFT,出题人想报复社会吧!/wx
细节:注意求的是有序点对还是无序点对!实现上求的是无序点对,所以长度 \(> 0\) 的链数记得乘 \(2\)。
6.4
第三次模拟赛。难度比以前降低了很多,赛时 100 + 70 + 12 = 182,rk 1/3。
三道题都没找到原题。个人难度评估:绿/蓝/紫。
A. 花园
现有一片 \(n \times m\) 大小的花园,即 \((1,1) \sim (n,m)\),每个位置有一个权值 \(c_{i,j}\)。
你有一个装置,当你将它放置在位置 \((x,y)\) 时,它将覆盖花园中所有形如 \((x+a_i,y+b_i)\) 的位置(其中 \(1 \leq i \leq k\),\(a\) 和 \(b\) 都是长度为 \(k\) 的数组)。
现在你想选择花园中的两个位置放置该装置,要求最大化被覆盖的位置的权值和。
\(1 \le n, m \le 100\),\(0 \le k \le 10\)。
太简单了。枚举一个位置,另一个位置的最大值用线段树维护。视 \(m = O(n)\),时间复杂度 \(O(n^2k^{2}\log n^2)\)。也有别的做法。
B. *先驱者
给定一张 \(n\) 个节点的由邻接矩阵 \(G\) 描述的边权非负的有向图,点编号为 \(1 \sim n\)。
定义 \(d(p, x, y)\) 为 途中 不经过 \(p\) 号点的情况下,从 \(x\) 号点到 \(y\) 号点的最短路径长度。特别地,若不存在这样的路径,定义该值为 \(-1\)。若 \(p = x\) 或 \(p = y\),则该值为无限制情况下的最短路径长度。
你需要求出下式的值:
\[\sum_{p=1}^n \sum_{x=1}^n \sum_{y=1}^n d(p, x, y) \]对于 \(50\%\) 的数据,\(n \le 50\)。对于 \(100\%\) 的数据,\(n \le 500\)。
还有 10 分给链,10 分给树。
50 pts 做法:跑 \(n\) 遍 Floyd,\(O(n^4)\)。
赛时我一直想着:可以分别从前往后和从后往前跑 Floyd,这样就能求出任意两点经过某个前缀点集/后缀点集的最短路,查询时可以合并两个点集的最短路。但我想了很久都没有想到快速合并两个点集最短路的做法。(实际上合并两个点集的最短路还是只能跑 Floyd,并且只要用到其中一个点集的最短路,然后枚举另一个点集中的点转移。赛时我想着能否利用另一个点集的最短路来加速合并的过程,但毫无前途地倒闭了。)
正解是 Floyd + 分治。其本质思想就是合并两个点集的最短路。具体而言,在 \(solve(l, r)\) 的过程中,要求出的是不经过点集 \([l, r]\) 的最短路。下一层递归到左边 \([l, mid]\) 时,把当前的最短路合并上 \([mid + 1, r]\) 点集;递归到右边 \([mid + 1, r]\) 时,把当前的最短路合并上 \([l, mid]\) 点集。到某个叶子节点 \([p, p]\) 时,就求出了不经过 \(p\) 的最短路。此时用 \(O(n^2)\) 的时间统计答案即可。
代码实现中,对于线段树的每一层,要开一个 \(O(n^2)\) 的数组暂存 Floyd 数组,用于回溯。
此算法的时间复杂度可用递归式 \(T(n) = 2T(\frac{n}{2}) + O(n^3)\) 表示,使用主定理解得 \(T(n) = O(n^3)\)。空间复杂度 \(O(n^2 \log n)\)。
C. 黄金矿工
小 T 在玩黄金矿工。
为了简化问题,我们假设矿工位于数轴原点,初始时有 \(n\) 块金子,且每块金子都位于数轴的正半轴上。第 \(i\) 块金子的坐标为 \(x_i\),价值为 \(v_i\)。保证序列 \(x_i\) 严格递增。获得第 \(i\) 块金子所需的时间为 \(x_i \cdot v_i\)。注意:与原版游戏不同,矿工可以花 \(x_i \cdot v_i\) 的时间直接获得某块金子,而不需要把它前面的金子先清理掉。
小 T 想知道在时限内能获得的金子价值之和最大是多少。但关卡有很多,每次通关后,金子以及时限会发生一些变化。具体来说,共有 \(m\) 次操作,操作有以下两种:
删除第 \(y\) 块金子,保证该金子之前没被删除过。
询问在有 \(k\) 个单位时间时,获得金子的价值之和最大是多少。注意:每块金子每次询问只能获得一次,且询问之间独立。也就是说,这次获得的金子在之后的询问中依然会出现。
你能帮小 T 在每个关卡都获得理论最高的价值吗?
\(1 \le n \le k_{\max} < 2 \times 10^6\), \(1 \le x_i \cdot v_i \le k_{\max}\),\(1 \le x_1 < x_2 < \cdots < x_n < k_{\max}\), \(1 < m < 5000\), \(1 \le k \le k_{\max}\)。
简记 \(k_{\max}\) 为 \(w\)。
省流:有大小不超过 \(w\) 的背包,\(n\) 个物品,大小和价值均不超过 \(w\),且物品的大小两两不同。求背包容量为 \(k\) 时物品价值的最大值。多次询问,动态删除物品。
背包问题如果没有特殊性质,只能做到 \(O(n \cdot w)\)。(把操作倒序,删除物品变成加入物品,不改变总时间复杂度。)所以一定要利用题目中的特殊性质。
题中最犀利的特殊性质是 \(x_i\) 两两不同。那么取一阈值 \(b\),满足 \(x_i \le b\) 的物品不超过 \(b\) 个。对于 \(x_i > b\) 的物品,其价值不超过 \(w / b\)。设 \(v_i = c\),则代价大于 \(c \cdot b\),因此价值同为 \(c\) 的物品最多选 \(\frac{w}{bc}\) 个,而且总是优先选 \(x\) 较小的。对 \(c\) 从 \(1\) 到 \(w / b\) 求和,可知 \(x_i > b\) 的物品中可能被选的物品数为
所以有用的物品总数为 \(O\left( b + \dfrac{w \log w}{b} \right)\),取 \(b = \sqrt{w \log w}\) 得 \(O(\sqrt{w \log w})\)。
先不考虑修改操作。另取一阈值 \(B\),对于 \(x \le B\) 的物品,设 \(f(i)\) 表示背包容量为 \(i\) 时的最大价值(\(i \le w\)),暴力 dp,由于这些物品的数论不超过 \(B\),因此这部分的时间复杂度为 \(O(w \cdot B)\)。对于 \(x > B\) 的物品,注意到它们的价值比较小。具体地,设选择的物品为 \((x_1, v_1), (x_2, v_2), \cdots, (x_p, v_p)\),那么
所以 \(v_1 + v_2 + \cdots + v_p\) 即价值的总和不超过 \(\frac{w}{B}\)。考虑翻转下标与状态,设 \(g(i)\) 表示获得 \(i\) 价值的所需的最少背包容量 (\(i \le \frac{w}{B}\)),则加入一个物品的时间复杂度为 \(O(\frac{w}{B})\)。如果使用有用的物品 dp,则总时间复杂度为 \(O(w\sqrt{w \log w}B^{-1})\)。为了做到这点,先把 \(x\) 从小到大排序(输入已保证有序),对每种 \(v\) 开一个桶,记录已经 dp 的 \(x = v\) 的物品的 \(x \cdot v\) 的总和,如果达到 \(w\) 则不用再 dp 了,因为我们总是贪心选择 \(x\) 较小的。
对于修改操作,还是先倒序,把删除改成加入。\(x \le b\) 的 dp 如常,对于 \(x > b\) 的 dp,记录 \(x \cdot v\) 总和的优化用不了了,因为不保证 \(x\) 有序。但修改操作比较少,所以不加优化也可以接受。
总时间复杂度为
为了方便,视 \(m\) 和 \(\sqrt{w \log w}\) 同级。使用均值不等式平衡,解得当 \(B = \sqrt{\sqrt{w \log w}}\) 时,有最优时间复杂度
6.5
80 + 100 + 40 = 220,rk 1/3。T2 太简单。
T1. 逆序对计算
给定一个长度为 \(n\) 的非负整数序列 \(a\)。\(q\) 次询问,每次询问给定非负整数 \(x\),求把序列中的每个数异或 \(x\) 后,序列的逆序对数量。
\(n, q \le 10^{6}\),\(0 \le a_{i}, x < 2^{31}\)。
套路性地考虑拆位。两个非负整数 \(x < y\) 等价于存在某一二进制位 \(p\),使得二者最高位到第 \((p + 1)\) 位都相同,而 \(x\) 的第 \(p\) 位为 \(1\),\(y\) 的第 \(p\) 位为 \(0\)。那么查询时可以枚举 \(p\) 统计答案。这需要预处理出:
- \(f(p, x)\),(\(x \in \{0, 1\}\))表示 \(a\) 中有多少个数对 \((i, j)\),满足 \(i < j\),\(a_i\) 和 \(a_j\) 的最高位到第 \((p + 1)\) 位相同,且 \(a_j\) 的第 \(p\) 位为 \(x\),\(a_i\) 的第 \(p\) 位为 \(x \operatorname{xor} 1\)。
最暴力的做法是枚举 \(p\),然后使用 map 统计(map 的下标是最高位到第 \((p + 1)\) 位的值),预处理时间复杂度 \(O(n \log n \log w)\),其中 \(w\) 是值域,可以获得 80 pts。
把 map 换成 Trie,然后所有位一起统计,时间复杂度变成 \(O(n \log w)\)。我怎么完全没想起用 Trie,烂完了。
T2. 边染色
有一棵 \(n\) 个节点的树,你需要将树的边进行染色,一条边可以染成黑色或者白色。有一个关键点 \(u\),一个染色方案合法当且仅当所有点到 \(u\) 的简单路径上至多有一条黑边。你需要回答 \(u = 1, 2, \cdots, n\) 时的合法染色方案数。模 \(10^9 + 7\)。
\(n \le 2 \times 10^{5}\)。
真不是普及组难度换根 dp?
T3. *哲学树
给定一棵有 \(n\) 个点的树,每个点 \(u\) 有两个属性 \(w_u\) 和 \(v_u\)。在第 \(t\) 秒,\(u\) 的点权为 \(w_u + t \cdot v_u\)。在树上行走,没经过一条边,时间增加 \(1\) 秒。\(q\) 次询问,每次询问给定正整数 \(s, t, x\),求出第 \(x\) 秒从 \(s\) 出发,沿树上唯一简单路径走到 \(t\),路径上所有正数点权的和。
\(n,q \le 3 \times 10^{5}\),\(1 \le x \le n\)。

浙公网安备 33010602011771号