杂题选做
「JOI 2017 Final」JOIOI 王国
- 给定带权的网格,在划分点单调的条件下将网格划分成两部分,使得各自部分的极差的 \(\max\) 尽量小。
- \(n,m\leq 2000,a_{i,j}\leq 10^9\)。
之前的 NOIP 计划的题目,刚好没补,这次把细节都想清楚了。
关键点在全局 \(\min/\max\) 要划分到不同区域,那么每个区域就有了 上/下 界,再二分答案就确定了值域。
简单判定是否满足要求的形态即可。
「JOI 2017 Final」足球
- 在一个 \(H\times W\) 的球场中,给定坐标内 \(n\) 个球员的初始站位 \((s_i,t_i)\),求将求从球员 \(1\) 脚下传到 \((s_n,t_n)\) 的最小代价。
- 某一时刻可以指定球员进行以下动作(可以多个球员同时运动,但一个球员不能同时进行多项操作):
- 踢球:将球按平行于坐标轴的 \(4\) 个方向之一踢出,并指定一个正整数 \(p\) 表示精准移动距离,球可以穿过其它球员,代价为 \(A\times p +B\)。
- 运球:某个球员朝指定方向带球移动 \(1\) 个单位距离,代价为 \(G\)。
- 无球跑动:某个球员朝指定方向移动 \(1\) 个单位距离,代价为 \(C\)。
- 控球:无球球员变成控球球员,当且仅当球员所在位置恰好有球时合法,无额外代价。
- 球员和球可以跑出球场。
- \(1\leq H,W\leq 500, n\leq 10^5,A,B,C\leq 10^9\)
性质找对了,但是没用好。
就是只需要考虑每个人带求,以及踢球到某个区域,然后让最近的人来找它,如果重复利用了一个人一定不优。
这样硬写一个类似 SPFA 的最短路算法会让单次扩展的数量过多,queue 直接 MLE。
发现 C 可以拆成每次上下左右走一格,因为可以变向,而踢球就因此不能。
但应当考虑到优化建图,带球的一层,上下方向一层,左右方向一层,预处理每个位置离最近的人的距离,随便建图跑最短路即可。
「JOI 2017 Final」绳
- 给定分为 \(n\) 段的绳子,有 \(m\) 种颜色,当且仅当折叠的绳子重合后对应段完全同色才可以折叠。
- 中途可以给某一段绳子染色,代价为这一段绳子当前的厚度。
- 求将绳子缩短为 \(2\) 的最小代价。
- \(m\leq n\leq 10^6\)。
以为是巨大神秘题的时候一定不能放弃,往往只是披了层皮,找到了性质就 win 了一半!
因为每次染色耗费层数代价,所以不如一开始就染好的优。
也就是染成至多两种颜色,简单展开分析一下,发现充要条件是,除了两头,所有同色连续段长度为偶数。
也即每种颜色段的开头位置的奇偶性相同,枚举对应颜色的时候可以分奇偶讨论,整体 \(O(N)\) 求出。
然后总代价就是 \(n-\text{sum}-\max\),其中 \(\text{sum}\) 是原序列中枚举到的颜色的个数,\(\max\) 是修改合法后剩余序列中个数最多的颜色的个数。
「CCO 2017」矩形帝国的霸业
- 求将 \(n\times m\) 的矩形划分为多个区域,每个区域为长宽比为 \(2:1\) 的矩形的 最小和最大 划分数。
- 需要满足可以从某个单个区域开始,每次增加一个区域,使得任意时刻所有区域都构成矩形。
- \(n,m\leq 10^8\)。
一眼搜索,但不能盲目的搜,多少得带点贪心。
设 \(n<m\),而两者相差很大,比如 \(m\geq 4n\),那么两种目标都有固定的贪心策略。
比如,最小化就一定是用 \(2n\),这是唯一决策,而最大化就是若 \(n\) 为偶数就用 \(n/2\),否则还是只能用 \(2n\)。
这样就跑的飞快了。
「CCO 2017」接雨滴
- 给定 \(n\) 个高度不同的柱子,可以任意排列它们来构成一个立体容器。
- 输出能构造的所有不同容积。
- \(n\leq 500,h_i\leq 50\)。
猜了个背包结论,然后就过了,巨大开心。
实际上证明的时候差一点脑筋,就是从大往小插入,不会出现有因为前面人要贡献而自己某些值贡献不了的。
因为若有:5 3 4
,这时候看似 2
插入没法贡献为 \(3-2=1\),但实际上可以 swap 两者,就是直接换成 5 2 4
,结果等价。
「CCO 2017」移动数组
神秘构造题,反正想清楚了也过了,纪念之。
「JOI 2016 Final」领地
- 给定一个长度为 \(n\) 的移动模式,每次上下左右移动一格单位长度。当一格网格的四个顶点都被经过过就成为占领了该网格。
- 循环执行 \(k\) 次,求占领的网格数。
- \(n\leq 10^5,k\leq 10^9\)。
多少有点无从下手,实际上,假设第一轮到了 \((a,b)\),那每次的点都是上一次对应节点平移一个 \((a,b)\) 向量。
启发性的按照 \(x\) 坐标 \(\bmod a\) 分类,将 \((x,y)\) 表示成 \((x'+ar,y'+br)\),其中 \(x'\in[0,a)\)。
也就是用 \((x',y',r)\) 表示一个坐标,虽然看似还多了一维,但是它在平移的时候有优美的区间形式。
它的出现时间就是 \((x',y')\) 在 \([r,r+k)\) 的一段。
枚举左上角的 \((x',y')\),这 \(4\) 个点的 \((x',y')\) 形式就都确定了,只需要看四个区间段的交的长度和即可。
当然不一定是同一个区间段出现,当 \(x'=a-1\) 时,\(x'+1\) 会到下一段,只需要将它的区间整体左移 \(1\) 即可。
「JOI 2016 Final」断层
- 有无穷深的地层,\(y=0\) 为地表,\(m\) 次操作,每次从 \((x_i,0)\) 沿某个 \(45\) 度方向上移动 \(l_i\) 层,超出地表的部分会消失。
- 最终,对每个 \(i\in[1,n]\) 询问 \((i-1,0)\) 到 \((i,0)\) 之间的地层是原本哪一层的。
- \(n,m\leq 2\times 10^5,|x_i|\leq 10^9,1\leq l_i\leq 10^9\)。
很妙的思维题。
看上去无从下手,实际上告诉终点求起点,多少应该想到逆向思维。
然后把坐标旋转 \(45^{\circ}\),这样每次变换都只改变一维坐标,而且是改变关于另一个坐标的偏序区间。
而且两种操作相呼应,使得序列具有单调性!直接树状数组 + 倍增即可,注意是超小常数单 \(\log\) 的,真是巧妙。
ARC142 D - Deterministic Placing
- 给定一棵树,每个节点上可能有至多一个标记,定义一次合法的移动为:
- 所有标记均移动到相邻的节点上。
- 每条边至多被经过一次。
- 每个点移动后至多有一个标记。
- 对于 \(2^n-1\) 种放置标记的初始局面,求优秀的局面数。一个局面是优秀的,当且仅当:
- 该局面都可以进行无穷步合法的移动,且每一步方案唯一。
- \(n\leq 2\times 10^5\)。
赛时被完虐。
实际上确实,你发现连通的关键点一定得同向移动,那肯定就是对应一组链划分。
稍加思考后得到,每条链显然有且仅有一个端点没有关键点。
大力讨论后得到,两条链,要么是一个有和一个没有的两个端点处相交,要么在两个都不是端点处相交。
然后设 \(5\) 种状态,大力讨论乘贡献树形 DP 转移即可。
「JSOI2019」节日庆典
- 给定字符串,对每个前缀求最小表示。
- \(n\leq 3\times 10^6\)。
发现需要考虑的起点,无非是当前出现过的最小的字符,当然这样的字符个数可能很多。
但是观察到,如果两个分别以 \(x,y\) 为起点的后缀,在不跨过当前前缀的情况下能分出胜负,那么字典序大的是无用的。
如果不能分出胜负,设当前前缀为 \(1\sim i\),\(\text{len}=i-y+1\) 且 \(x<y\),那么相当于 \([x,x+\text{len}-1]=[y,i]\)。
发现,如果这两个区间相交,说明出现了循环节(最后一个可能不完整),而循环节只需要考虑第一个和最后一个循环即可!!!
而如果不相交,那么 \(2\times \text{len}\leq i-x+1\),即当前点距离当前前缀末尾的距离减半,故至多有 \(O(\log n)\) 个备选起点!
利用这个性质直接干活是 \(O(n\log^2 n)\) 的,因为还有性质没用到。
备选的任意两个后缀,较短的都是较长的前缀,简单讨论后发现,删去相同前缀后,两个循环同构串的比较总是有一个指针在第 \(1\) 个字符!
所以直接对每个位置求 z 函数就能 \(O(1)\) 比较了!
「LibreOJ β Round #2」数学上来先打表
- 给定图,点有点权,初始无边。
- 支持加无向边,回溯到历史状态,和查询连通块第 \(k\) 小点权。
- \(n\leq 10^5\)。空间 \(\mathbf{50\ Mib}\)。
还是挺妙的 QwQ
发现一众合并数据结构都基于均摊,这个历史回溯瞬间打回原形。
同时注意到空间限制,所以要求在空间开销较小的情况下使合并不基于均摊,且可撤销。
不难想到值域分块,离散化后对每个可撤销并查集维护 \(\sqrt N\) 个块中分别有多少个数。因为“个数”这个信息是可减的,所以可以方便的撤销。
确定 \(k\) 小值在哪个块后,直接依次枚举,看对应点是否在集合内即可。
假设块长为 \(B\),那么时间复杂度 \(O(m \log n\max(B,\dfrac{n}{B}))\),空间复杂度 \(O(\dfrac{n^2}{B})\)。适度放大块长以保证空间复杂度是合理的。
看一眼 std 发现可以对 bitset 缩点,然后查询直接遍历 bitset(相当于按 \(64\) 位分块),时间复杂度 \(O(\dfrac{nm}{64})\)。
空间上带点启发式合并的思想,每次保留较小数组以回退,做到了 \(O(n\log n)\)。和分块做法各有千秋吧。
ARC144 D - AND OR Equation
- 求下标为 \([0,2^n)\) 的数组 \(f\) 的个数,满足:
- \(f(x)\in[0,k]\)。
- \(f(x)+f(y)=f(x\operatorname{AND} y)+f(x\operatorname{OR} y)\)。
- \(n\leq 3\times 10^5,k\leq 10^{18}\)。
条件转化没有彻底……
不难发现只要确定了 \(\forall i\in[0,n),f(2^i)\) 的值,就确定了所有数。
假设一个数 \(x\) 有 \(t\) 个二进制位为 \(1\),且分别为 \(o_1\sim o_t\),那么有:
这时候应当想到把 \((t-1)\) 个 \(f(0)\) 减到和式里面去,不如和二进制位个数相关的项很难处理 /fn/fn/fn
不妨设 \(f_i=f(2^i)-f(0)\),特别的 \(c=f(0)\),那么就有:
每一个 \(f(x)\) 都要满足 \(\in[0,k]\)。
不难发现,最容易突破下界的,就是所有 \(<0\) 的 \(f_i\) 的组合;上界同理就是所有 \(>0\) 的 \(f_i\) 组合。
分别设为 \(F^-\) 和 \(F^+\),那么就是要满足:\(F^-+c\geq 0,F^++c\leq k\)。
求 \(c\) 的值域,发现是:\([-F^-,k-F^+]\),区间长度为 \(k-F^++F^-+1=k-\sum|f| +1\)。
那么题意转化为:
- 求所有 \(\sum|f|\leq k\) 的长度为 \(n\) 的序列的 \(k-\sum|f|+1\) 的和。
先考虑 \(\sum |f|\),发现对应序列 \(<1,2,2,2,\cdots>^n\),它的生成函数为 \(\left(\dfrac{1+x}{1-x}\right)^n\)。
之后还有一个和卷积,不难发现可以卷上 \(<1,2,3,4,\cdots>\),也就是 \((1-x)^{-2}\)。
所以答案就是 \([x^{k}] \dfrac{(1+x)^n}{(1-x)^{n+2}}\)。
分子的展开只有 \(n+1\) 项,然后易得 \([x^k] (1-x)^{-(n+2)}=\dbinom{n+k+1}{k}\)。所以:
翻翻题解发现还有个组合意义很妙。就是考虑先枚举有多少位非 \(0\),接着 \(2^i\) 表示枚举它们的符号。
之后相当于求长度为 \(i\) 的正整数序列 \(<a_1,a_2,\cdots,a_i>\) 满足 \(\sum a\leq k\) 的 \(k+1-\sum a\) 的和。
等价于 \(<b,a_1,a_2,\cdots ,a_i>\) 满足 \(b+\sum a\leq k+1\) 的正整数序列个数!!!
等价于 \(<c,b,a_1,a_2,\cdots,a_n>\) 满足 \(c+b+\sum a=k+2\) 的正整数序列个数!!!
然后就是一个 \(i+2\) 项,和为 \(k+2\),强制全为非 \(0\) 的插板法,得到:
【集训队互测2015】胡策的小树
- 给定一棵 \(n\) 个节点的树,保证以随机父亲的方式生成。
- 每个点有点权,点权是 \(0\sim n-1\) 的排列,且 \(a_1=0\)。
- 初始每个节点各有一只猴子,每只猴子每秒都会尝试跳到父亲,以 \(p_i=\dfrac{a_i}{n}\) 的概率成功。
- 如果失败,则会随机到达 \(i\) 的子树内的某个节点(包括 \(i\) 自己)。
- 令 \(g_i\) 表示第 \(i\) 秒时成功跳到父亲的猴子的占比,求过了 \(\infty\) 时间后, \(g\) 的平均值的期望。
- 可以选择一个 \(x\) 使得 \(a_i\gets (a_i+x)\bmod n\),求最大的期望值。
- \(n\leq 5\times 10^6\)。
题意的复杂性是这道题难点的一部分,需要有效的利用 \(\infty\) 时间这个条件来简化问题。
首先,既然是 \(\infty\) 时间,那么每只猴子对期望的贡献可以看作是一样的。
先考虑 \(x=0\) 的情况,设 \(f_i\) 表示某一时刻猴子在节点 \(i\) 的概率,则 \(\text{ans}=\sum f_ip_i\)。不难得到方程:
其中 \(\text{ancestor(u)}\) 表示 \(u\) 的祖先集合,包含 \(u\) 节点本身。
当 \(u=1\) 时方程无用,但加上 \(\sum f=1\) 就可解了,直接高斯消元复杂度为 \(O(n^3)\)。
但是树上高斯消元有众所周知的设 \(f_u=K_uf_{\text{fa}_u}+B_u\) 的套路,这题也类似,但是考虑到 \(v\in \text{ancestor}(u)\) 这个条件比较难处理。
考虑设:
不难得到:
特别的有 \(g_1=\dfrac{f_1}{\text{siz}_1}\)。
这样用树上高斯消元能得到 \(g_i\) 和 \(g_1\) 的关系,也就能得到 \(f_i\) 和 \(f_1\) 的关系,再用 \(\sum f=1\) 就完成了。
考虑能自由选择 \(x\) 的情况,貌似很棘手,实际上还是利用 \(\infty\) 时间这个条件。
因为那个 \(a_i=x\) 的 \(i\) 总会变为 \(0\),而在 \(\infty\) 时间内,猴子会以 \(1\) 的概率到达这个节点或其子树,而一旦到达就出不来了。
所以相当于直接在该子树内求解即可。
这样时间复杂度是 \(O(\sum \text{siz})\) 的,在随机父亲的生成方式下,可以证明是 \(O(n\log n)\) 的。
AGC058 C - Planar Tree
- 按顺时针给出圆周上 \(n\) 个点的权值 \(a_i\in\{1,2,3,4\}\),两点之间能够连边当且仅当 \(|a_i-a_j|=1\)。
- 判断图是否存在一棵生成树,使得任意边不在非端点处相交。
- \(n\leq 3\times 10^5\)。
赛时感觉很有感觉,但是没有结果。
首先这种圆上不相交已知只有两种方法:栈模拟 / 不断合并。
用栈模拟大概指的是时刻维护能够与之连边的先前点,当然前提是决策基于某种无后效性的策略,很可惜这里并不适用。
考虑合并相邻项,首先相邻且相等显然可以直接合并而不影响有解性。
其次如果 1 2
相邻或 3 4
相邻那么 1
或 4
可以果断合并掉,因为 1 4
看起来只是个累赘(
如果说目前画风还算正常,后面就比较神秘了。
称上述操作为“缩合”,之后每进行一步操作都默认立刻尽可能的进行“缩合”来简化局面。
不难发现,任意一个合法生成树都存在边 \((a,b)\) 使得 \(a,b\) 在圆上相邻,且 \(a,b\) 中存在叶子。那就可以通过剥叶子的方式还原。
在“缩合”后,只有 2 3
相邻可能存在,把它们中的某一个当做叶子,看影响。
- 如果删除
2
:- 如果局面为
3 2 3
,会删除 \((3,2)\)。 - 如果局面为
4 2 3
,会删除 \((4,2)\)。
- 如果局面为
- 如果删除
3
:- 如果局面为
2 3 2
,会删除 \((3,2)\)。 - 如果局面为
2 3 1
,会删除 \((3,1)\)。
- 如果局面为
所以,能够删除的对就是 \((1,3),(2,4),(2,3)\)。且不难发现如果存在 \(1/4\) 那么在“缩合”后的局面下一定能够被立刻删除。
所以 \(1,3\) 和 \(2,4\) 相当于每时每刻都能一命换一命,最终合并到只剩下一些 \(2\) 和一些 \(3\) 那么就 win 了。
所以得到充要条件:答案是 yes
当且仅当初始局面“缩合”后,剩余的 \(\text{num}(2)>\text{num}(4),\text{num}(3)>\text{num}(1)\)。
AGC058 D - Yet Another ABC String
- 给定 \(a,b,c\),求有 \(a\) 个
A
,\(b\) 个B
和 \(c\) 个C
,且不包含ABC
/BCA
/CAB
作为子串的字符串个数。 - \(1\leq a,b,c\leq 10^6\)。
赛时一直莽 C 所以没怎么想 D。
显然是容斥,钦定某些长度为 \(3\) 的子串为不合法串,容斥系数显然是 \((-1)^{\text{num}}\)。如果子串间有重合就直接合并。
套路的拆开来对每个区间计算容斥系数之和,显然仅和区间长度 \(x\) 有关,且已知 \(f(x)=-\sum_{i=1}^2 f(x-i),f(1)=1,f(3)=-1\)。
其中等式表达的是每 \(3\) 个 \(f(x)+f(x+1)+f(x+2)\) 均为 \(0\),那么就很容易得到:
以及每个区间的方案数,值考虑 \(f(x)\neq 0\) 的两种:
- 对于 \(x\bmod 3=0\),有 \(3\) 种不同的方案,消耗相同数量的
A,B,C
。 - 对于 \(x\bmod 3=1\),有恰好一种比其他两种多 \(1\) 个,确定这种字母之后方案唯一。
很可惜不能暴力的将这些区间组合起来,因为 \(a,b,c\) 的限制仍然需要表达在 DP 状态中,很难受。
考虑将这些被钦定的区间们拆成 \(3+3+\cdots +3(+1)\),其余均当作 \(+1\) 处理,实际上所有 \(3\) 的个数相等的序列可以被一起计算!
方法是枚举 \(3\) 的个数 \(t\),考虑按照最后一个被钦定的区间是 \(+3\) 结尾还是 \(+1\) 结尾来划分。
对于剩余的 \(+3\) 们,如果选择和后面一个数字连接,那么贡献为 \(1\),否则为 \(-3\)(方案数 \(\times\) 容斥系数),加起来是 \(-2\)。
而剩余的无论 \(+1\) 还是普通的当做 \(+1\) 的未被钦定位,它们的贡献永远是 \(1\)。
所以,如果 \(+1\) 结尾,整体系数就是 \((-2)^t\);如果 \(+3\) 结尾,整体系数则是 \((-2)^{t-1}\times (-3)\)。
这题巧妙之处在于,容斥系数的组合可以直接相加/相乘大大加快计算速度,同时找到了一个好的划分方法。
code
// Problem: D - Yet Another ABC String
// Contest: AtCoder - AtCoder Grand Contest 058
// URL: https://atcoder.jp/contests/agc058/tasks/agc058_d
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
const int N = 3e6 + 10;
int a, b, c, p[N], fac[N], inv[N], vinv[N];
int qpow(int a, int b) {
int s = 1;
for(; b; b >>= 1, a = 1ll * a * a % P) if(b & 1) s = 1ll * s * a % P;
return s;
}
void prework(int n) {
fac[0] = p[0] = 1;
rep(i, 1, n) fac[i] = 1ll * fac[i - 1] * i % P, p[i] = plu(p[i - 1], p[i - 1]);
inv[n] = qpow(fac[n], P - 2);
per(i, n, 1) inv[i - 1] = 1ll * inv[i] * i % P;
rep(i, 1, n) vinv[i] = 1ll * inv[i] * fac[i - 1] % P;
}
int calc(int a, int b, int c, int d) {
return 1ll * fac[a + b + c + d] * inv[a] % P * inv[b] % P * inv[c] % P * inv[d] % P;
}
int get(int x) {return (x & 1) ? P - 1 : 1;}
int main() {
a = read(), b = read(), c = read();
prework(a + b + c);
int ans = 0;
rep(t, 0, min(a, min(b, c))) {
int o = a + b + c - 3 * t;
int v = calc(a - t, b - t, c - t, t);
int x = 1ll * v * o % P * vinv[t + o] % P;
int y = 1ll * v * t % P * vinv[t + o] % P;
add(ans, 1ll * x * p[t] % P * get(t) % P);
if(t) add(ans, 1ll * y * p[t - 1] % P * get(t - 1) % P * (P - 3) % P);
}
printf("%d\n", ans);
return 0;
}
「JOI 2018 Final」团子制作
- 给定 \(n\times m\) 的团子们,有三种颜色
R G W
。 - 只有 从上到下 或 从左往右 连续 \(3\) 个恰好按顺序为
RGW
的团子可以串成串,且串之间不能有交。 - 求至多能得到多少个串。
- \(n,m\leq 3000\)。
最直接的想法,发现竖直的和水平的互相之间是不会有交的,所以建出二分图跑最大独立集。
结果发现网络流直接 MLE 了……
然后把图一画发现不能以 R
为代表元,而是 G
。
这样只有 G
在同一对角线的团子们有影响,这样就按对角线转化成了 \(n+m-1\) 个线性 DP。
「JOI 2018 Final」月票购买
- 给定带权无向图,可以选择任意一条 \(s\to t\) 的最短路并将路径上的所有边权变为 \(0\)。
- 使得新图种 \(u\to v\) 的最短路最短,求这个最小值。
- \(n\leq 10^5,m\leq 2\times 10^5\)。
这题是简单的,但是反应了挺久。
唯一有用且显然的性质,就是总有一种最优情况,选择的 \(s\to t\) 的最短路与 \(u\to v\) 只会有一段连续的交。
预处理每个点到 \(s/t\) 的最短距离后,根据路径状况设计 \(f(u,0/1/2/3)\) 即可,转移还是 Dijkstra 的形式。
「JOI 2018 Final」毒蛇越狱
- 给定 \([0,2^n)\) 个状态对应的权值,\(m\) 次询问。
- 每次给出长度为 \(n\) 的字符串,包含字符 0/1/?,其中 ? 表示可以是 0/1。
- 求所有合法状态的权值和。
- \(n\leq 20,m\leq 10^6\)。
小清新思维题。
设一次询问中的 0/1/? 的个数分别为 \(\text{cnt}_{0/1/2}\)。
首先一个单次 \(O(2^{\text{cnt}_2})\) 的做法是显然的。
难点是发现 \(\min(\text{cnt}_i)\leq 6\),从而想到分治的做这些情况,现在唯一需要解决的是 \(0/1\) 个数较少的情况。
对于 \(1\) 来说,可以令 \(?\) 都变成 \(1\),然后用预处理的高维前缀和求答案。但是固定的 \(1\) 不能变成 \(0\),所以直接 \(2^{\text{cnt}_1}\) 容斥即可。
对于 \(0\) 是类似的,只是变成高维后缀和。
ARC153 D - Sum of Sum of Digits
- 定义 \(f(x)\) 表示 \(x\) 的各个数位之和,如 \(f(153)=1+5+3=9\)。
- 给定序列 \(a_i\),可以自行选择一个非负整数 \(x\) 来最小化 \(\sum f(a_i+x)\),输出这个最小值。
- \(n\leq 2\times 10^5,1\leq a_i<10^9\)。
被杀了/ll
从两个不同角度思考问题就能得到满意的结果!
赛时思路,考虑从小到大对 \(x\) 的每一位 DP,如果某一位选 \(c\) 那么会贡献 \(n\times c\) 的和,同时每次进位都会有 \(-9\) 的贡献。
但是问题是不能直接记录 \(f(p,v)\) 表示 DP 到第 \(p\) 位,\(x\bmod 10^p=v\) 的最小值,值域太大了。
大胆猜想,对每个 \(i\) 只保留 \(O(n)\) 个不同的 \(v\) 值即可完成 DP,例如 \(v\in \{10^p -a_i\bmod 10^p\}\)。可惜假的离谱。。。
实际上,完全可以在每次考虑完第 \(p\) 位后令 \(a'_i=\lfloor a_i/10\rfloor\),这样就可以只根据当前填的数来判断能否进位。
同时,还是从宏观考虑,考虑将序列按照 \(a_i\bmod 10^p\) 升序排序,那么能发生进位的一定是一段后缀,而转移时只需要记录断点即可!
code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
#define eb emplace_back
inline int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
const int N = 2e5 + 10, B = 10, inf = 1e9;
int n, a[N], f[N], g[N], p[N], q[N]; ll b[B + 5];
int v[B + 5], w[B + 5];
int main() {
n = read(), b[0] = 1;
rep(i, 1, n) a[i] = read();
rep(i, 1, B) b[i] = b[i - 1] * 10;
int sum = 0;
rep(i, 1, n + 1) f[i] = g[i] = inf;
f[n + 1] = 0;
rep(i, 1, n) p[i] = q[i] = i;
rep(o, 1, B) {
vector<int> nxt[10]; int t = 0;
rep(i, 1, n) nxt[a[q[i]] / b[o - 1] % 10].eb(q[i]);
rep(i, 0, 9) for(int v : nxt[i]) p[v] = ++ t;
rep(i, 0, 9) v[i] = 0, w[i] = n + 1;
auto cmin = [](int &x, int y) {x = x < y ? x : y;};
rep(i, 1, n) {
int x = a[i] / b[o - 1] % 10; sum += x;
rep(y, 10 - x, 9) v[y] ++, cmin(w[y], p[i]);
}
per(i, n + 1, 1) {
if(i <= n) {
int x = 10 - (a[q[i]] / b[o - 1] % 10 + 1);
v[x] ++, cmin(w[x], p[q[i]]);
}
if(f[i] < inf) rep(c, 0, 9) cmin(g[w[c]], f[i] + c * n - v[c] * 9);
}
rep(i, 1, n + 1) q[p[i]] = i, f[i] = g[i], g[i] = inf;
}
int ans = inf;
rep(i, 1, n + 1) ans = min(ans, f[i]);
printf("%d\n", ans + sum);
return 0;
}
【CTS/WC2023】楼梯
- 给定阶梯状网格图,定义一个格子的权值为以它为左上角的子阶梯的上边缘和左边缘格格数。
- 初始网格图为空,支持 \(4\) 种操作:
- 在前 \(a\) 行末尾插入 \(b\) 格。
- 将第 \(a\) 行(包括)以下的所有行删除 \(b\) 格,不足则删空。
- 撤销操作,保证撤销区间不交。
- 询问 \(q\),求是否存在格子的权值为 \(q\),保证其为当前楼梯边界格数的因子。如果存在给出任意方案,否则输出
-1
。
- \(m\leq 3\times 10^5,a,b\leq 10^9,q\leq 10^{18}\)。
闲话:
赛时写了个暴力维护连续段的 \(O(n^2)\) 暴力,被 freopen("stairs.out", "w", stdin)
送走了,太松懈了些(
个人感觉 zzq 老师这题出的好优美啊,\我是 zzq 老师的小迷弟/
据说是杨表的结构,但没过多了解。
正题:
这个因子的性质看上去挺迷惑的,感觉如果真正用上了就基本上正解了。
关键转化:将二维的表格通过勾画轮廓线的方式转化成一维序列问题,因为是阶梯所以只有两种变化方式。
来个官方题解的图:
将竖线设为 \(1\),横线设为 \(0\),那么上图可以转化为:\([1,1,0,1,0,0,1,1,0]\),此时轮廓线长度 \(n=9\)。
这样转化后就有了一个优美的性质:每一个 \(a_l=1,a_r=0\) 的区间 \([l,r]\) 都和某个网格的生成子阶梯形成双射。
例如上图中网格 \((2,2)\) 就对应了区间 \([2,6]\) 即序列 \([1,0,1,0,0]\) 这一段。
与此同时,该网格的权值也有了很好的表达,因为区间勾画了子阶梯的周长,而不难发现权值就是 \((r-l+1)-1=r-l\)。
对于询问 \(q\),考虑一个特殊的子序列:\([a_1,a_{q+1},a_{2q+1},\cdots,a_{n-q},a_n]\)。
注意到 \(n\) 实际上就是边界个数 \(+1\),即询问的性质即为 \(q\mid n+1\)。所以该序列一定以 \(a_n\) 结尾。
如果该序列中存在相邻的 \(1,0\) 就找到解了,同时因为 \(a_1=1,a_n=0\),所以这种情况是必然出现的,除非 \(n\leq 1\)。
而显然只要 \(n>0\) 就有 \(n\geq 2\)。所以,对于 \(n=0\) 必然无解,否则必然有解,这是令人兴奋的又一个发现。
而构造的方法不过是从区间 \([l,r]\) 还原到它对应的网格,显然是 \((\sum_{i=1}^l a_i, \sum_{j=r}^n 1-a_j)\)。
现在只剩下怎么维护序列了,考虑操作们:
- 插入。即在第 \(a\) 个 \(1\) 后加入 \(b\) 个 \(0\)。
- 删除。删除序列末尾的 \(b\) 个 \(0\),并在第 \(a\) 个 \(1\) 前插入 \(b\) 个 \(0\)。如果第 \(a\) 行不足 \(b\) 个格子需要特殊考虑,平凡。
- 回溯。直接暴力回溯或者可持久化复杂度都有保证。
- 询问。观察到这个询问是可二分的,对于提取出来的子序列直接看 \(a'_\text{mid}\),如果为 \(0\) 就往左,否则往右。
离散化后只需要维护每两个 \(1\) 之间有多少个 \(0\) 就好了,这些都可以用(可持久化)线段树维护。
询问是瓶颈,两只 \(\log\)。