触底是会反弹的?触底是会反弹的!

CF1748E Yet Another Array Counting Problem

显然的 dp。

注意到最左边最大值,考虑笛卡尔树。显然我们要求 \(b\) 的笛卡尔树和 \(a\) 的笛卡尔树树同构,所以我们可以直接把 \(a\) 的笛卡尔树建立出来然后树形 dp,显然设 \(f_{i,j}\) 表示节点 \(i\) 对应区间最大值是 \(j\) 的方案数。然后转移:

\[f_{i,j}\gets f_{ls(i),k}\times f_{rs(i),l},\forall k<j\land l\le j \]

这是两个前缀和卷积形式,直接前缀和优化即可。

[AGC006D] Median Pyramid Hard

看到中位数和大小关系,要求一个很奇怪的值,想到二分答案。二分之后我们把大于等于它的数设置为 \(1\),小于它的数设置为 \(0\),然后考虑找到最大的使得塔尖是 \(1\) 的数就是答案。

使劲观察。可以发现在底端任何一个长度大于 \(1\) 的连续段都会在下一层继承上一层并往两侧扩展(如果两侧的连续段长度为 \(1\) 的话)。而一个独立的长度为 \(1\) 的连续段则会不断反色向上。

于是分类讨论:

  • 如果中间位置在一个长度大于 \(1\) 的连续段中:它会保持现状到最上方。
  • 反之:
    • 如果存在长度大于 \(1\) 的连续段:每一次每个连续段会向两侧扩展,所以找到离它最近的这种连续段。因为最后会被扩展到,颜色就是这个最近连续段的颜色。显然不会存在颜色不同的最近这种连续段。
    • 如果不存在,则它一直反色向上,讨论奇偶性即可。

CF585E Present for Vitalik the Philatelist

不是,我常数怎么这么大的、、、

考虑枚举子集 \(\gcd\),容易使用约数容斥计算 \(f(i)\) 表示 \(\gcd\) 恰好是 \(i\) 的子集数量。

然后考虑 \(x\),可以发现 \(x\) 因为和子集的 \(\gcd\)\(1\) 而子集的 \(\gcd\) 不是 \(1\) 所以 \(x\) 必然不在子集中,那是个废限制。考虑计算 \(g(i)\) 表示和 \(i\)\(\gcd\)\(1\) 的数的数量。

直接莫反启动:

\[\begin{aligned} f(i)&=\sum\limits_{j=1}^n [\gcd(a_j,i)=1]\\ &=\sum\limits_{j=1}^n\sum\limits_{k|\gcd(a_j,i)}\mu(k)\\ &=\sum\limits_{j=1}^n\sum\limits_{k|a_j,k|i}\mu(k)\\ &=\sum\limits_{j=1}^n\sum\limits_{k=1}\mu(k)[k|i][k|a_j]\\ &=\sum\limits_{k=1}\mu(k)[k|i]\sum\limits_{j=1}^n[k|a_j] \end{aligned} \]

后面那个求和相当于要求 \(k\) 是多少个数的因数,开桶计数 \(a_i\) 后即可调和级数。本题中的枚举因数统计都可以改成桶做到调和级数。

前面那个求和可以枚举 \(k\) 对所有 \(i\) 的贡献做到调和级数。

约数容斥复杂度本身就是调和级数。

总复杂度 \(\mathcal O(V\ln V)\),不知道为什么我常数特别大直接跑了 4.9s。有点卡空间。

[JOISC2014E] 水壶

考虑一个 naive 的想法,我们把整个完全图建出来,两两建筑物之间的边权为它们的最短路径长度。然后我们希望求得一个最小的边权 \(x\) 满足只考虑 \(\le x\) 的边使得 \(S,T\) 连通。

这就是货车运输。根据 truck's trick,我们造一个最小生成树,然后考虑最小生成树上 \((S,T)\) 路径的最大边权即可。容易用最小生成树定义证明不可能有更小的最大边权。

但是我们不可能把完全图造出来。你发现完全图上有极多废边,这种 \(x\to y\) 的边之所以废就是因为 \(x\to z\)\(z\to y\) 都比它还短,那么 \(x\to y\) 就一定不会被选择到最小生成树里面。如此递归下去,我们可以把废边拆成若干条有用边。

考虑有用边满足什么性质。这里薅一张 JOI 的图:

我们对网格图进行多源 BFS 染色,这样可以求出来每个点最近的建筑物在哪里。有一个很强的性质可以证明:任何一条有用边在网格图上的路径都不会经过两种以上颜色。

Proof.
考虑 \(x\to z\to y\),其中 \(x,y,z\) 这三者颜色互不相同,且 \(x,y\) 是建筑物。令 \(z'\)\(z\) 的颜色对应的建筑物,可以证明:

  • \(x\to z'\) 的距离小于 \(x\to z\to y\)。因为 \(z\) 这个点属于 \(z'\) 的颜色,所以 \(z\to y\) 肯定比 \(z\to z'\) 远,所以 \(x\to z\to z'\) 的距离小于 \(x\to z\to y\)
  • \(z'\to y\) 的距离小于 \(x\to z\to y\)。因为 \(z\) 这个点属于 \(z'\) 的颜色,所以 \(z'\to z\) 肯定比 \(x\to z\) 近,所以 \(z'\to z\to y\) 的距离小于 \(x\to z\to y\)

所以,把废边 \(x\to y\) 拆成 \(x\to z'\)\(z'\to y\) 就比原来好。一直拆下去就可以拆出有用边,而所有有用边必然满足不会经过两种以上的颜色。

考虑到任意一条边必然至少会经过两种颜色(毕竟必须要连接两个不同建筑物),所以任何一条有用边都会恰好经过两种颜色。而两种颜色必然有一个分界点。

所以做法就呼之欲出了。我们直接跑一遍多源 BFS 给网格图染色,在颜色分界点处统计加边,这样最多只会有四倍网格图大小的边,但实际上远远跑不满。弄完之后随便拉一个最小生成树算法像完全图一样做就行,稠密图推荐使用 Prim,但是 Kruskal 也过了。

P4632 [APIO2018] 新家

简单的黑题。

首先一眼过去就是一个把时间离线,扫时间轴,插入删除商店。然后我们考虑每个点附近某个类型最远的商店是哪里。

一个比较灵感的二分:考虑最近的有所有类型商店的位置。乍一看 check 是一个二维数点,但是我们只需要考虑是不是有所有商店,所以我们只想知道区间右边有没有 \(pre\) 在区间左边的点,这种类型的点就不会在区间里面出现,那么区间里面就肯定没有所有类型的点了。

写起来略麻烦,但是也还好。

CF542B Duck Hunt

比较神秘的黑题。

首先我们容易反应过来,所有 \(0\) 左边的部分是无用的。那么把相对移动换一下,我们就是取相距至少 \(r\) 的一堆点,满足覆盖这些点的区间最多。

然后你容易想到一个 dp:\(f_{x,0/1}\) 表示考虑到坐标 \(x\),坐标 \(x\) 处是否开枪,能打的最大鸭子数量。然后你马上反应过来这不行,因为前面开枪了会有后效性,一些鸭子消失了,但是后面的状态并不知道前面有没有开枪。

所以把上次开枪的位置也塞进去。\(f_{x,i}\) 表示考虑到坐标 \(x\),上次开枪在坐标 \(i\) 能打的最大鸭子数量。这样我们每次往后就能知道还有哪些鸭子活着。

你考虑怎么向右移动 \(x\) 转移。每次会进来一大堆覆盖这个点的鸭子,然后检查这些点和 \(i\) 有没有交。有交的鸭子早就死了,没交的鸭子可以在这个点上打。所以首先转移 \(f_{x,i}\gets f_{x-1,i}\),其次 \(f_{x,x}\) 表示在这个点上开一次枪,那么转移 \(f_{x,x}\gets \max\limits_{y=0}^{x-m} f_{x-1,y}+c_y\),其中 \(c_y\) 表示覆盖 \(x\) 不覆盖 \(y\) 的鸭子的数量,也就是 \(y<l\le x\) 的鸭子的数量。

然后你发现这个做法没有前途。因为我们需要把鸭子下放到覆盖的所有位置中去才能计算 \(c_y\),而这是不可能的,所以我们考虑这个 \(y<l\le x\) 的本质不同位置。我们发现,只有鸭子的端点处是关键点,会产生不一样的 \(c\),所以我们直接把鸭子扔到一个端点处。考虑放到左端点,因为我们在向右走和开枪所以肯定不太方便(但是似乎也能做),而且我们的朴素转移过程本质上是在拓展鸭子的右端点,那么还是放到右端点非常完美。

再次修改状态!考虑 \(f_{x,i}\) 表示考虑所有 \(r\le x\) 的鸭子,上次开枪在坐标 \(i\) 能打到的最大鸭子数量。每次向右移动,我们只需要考虑所有 \(r=x\) 的鸭子。

转移时,同样我们首先进行 \(f_{x,i}\gets f_{x-1,i}\),但是这里要加上 \(l\le i\) 的鸭子数量。然后考虑 \(f_{x,x}\gets \max\limits_{y=0}^{x-m} f_{x-1,y}\),此时的 \(f_{x-1,y}\) 同样要加上 \(l\le y\) 的鸭子数量,然后再加上 \(y<l\) 的鸭子数量表示被 \(x\) 打的,那么合在一起其实就是所有的鸭子,这也很好理解。

这个状态没有什么可优化的点了,但是注意到继承关系我们想到上整体 dp。考虑转移的数据结构意义:

  • \(f_{x,i}\) 的转移拆贡献,那么就是枚举鸭子进行一个区间加;
  • \(f_{x,x}\) 的转移就是赋前缀 \(\max\),然后再加上鸭子的数量,后者拆贡献也可以拆成区间加。

所以我们要支持区间加 \(1\),单点赋前缀 \(\max\)。由于我们不可能枚举所有坐标,所以我们优化单点赋前缀 \(\max\) 的过程。

[TBC]

[ABC262Ex] Max Limited Sequence

有点精妙的东西!

我们首先不考虑无解的情况,假设所有限制都是互不排斥可以共存的。

那么你考虑一个限制实质上是设置了两个限制:\(\exist i\in[l,r],a_i=X\)\(\forall i\in [l,r],a_i\le X\)。考虑后面那个限制显然更强,限制了每个数的上界,所以我们先直接求出每个数的上界。

然后考虑前面一条限制本质上是要求 \([l,r]\) 内必须存在一个数抵达 \(X\) 的界,我们直接考虑设计 dp \(f_{i,j}\) 表示前 \(i\) 位,上一次达界的数是第 \(j\) 个。但是我们需要避免不同界之间的干扰(比如达了另一个区间的界也叫达界)。我们试图把相同 \(X\) 的限制一起做。

考虑现在所有限制为 \(X\) 的限制,那么上界小于 \(X\) 的数是无关紧要的,而上界大于 \(X\) 的数一定与 \(X\) 的限制完全无关。所以我们只需独立考虑上界等于 \(X\) 的数。

这时进行那个 dp 就非常合理。考虑转移:

  • 如果这一位上是 \(X\),那么 \(f_{i,i}\gets f_{lst,j}\)
  • 如果这一位上不是 \(X\),那么 \(f_{i,j}\gets X\times f_{lst,j}\)

然后考虑这一位上挂的 \(r=i\) 的限制,如果 \(f_{i,j}\)\(j\) 小于 \(\min l\) 则推成 \(0\)。实现时不能直接挂到 \(r\) 上,而是要挂到 \(r\) 左边最近的是 \(X\) 的地方,因为那个位置是最后一个可以满足限制的地方。

线段树随便做即可。当然,由于只有前缀推 \(0\) 和全局乘法,维护全局和,也可以双指针加标记加快速幂做,快速幂可以转为线性从而整个做到线性。

无解判定显然是看一个限制内的上界是否都不能满足这个限制,这是简单的。

CF756E Byteland coins

哎,优化无用状态题做少了啊。

显然的想法,\(f_{i,j}\) 表示用完前 \(i\) 种金币总金额为 \(j\) 的方案数。假设我们可以得到所有东西的真正面值 \(d_i=\prod\limits_{j=1}^{i} a_j\)\(d_i\) 表示 \(i+1\) 号金币的面值。特别地,\(d_0=1\)\(d_n=+\infty\)

考虑对于每个 \(f_i\) 上只有 \(j\equiv m\pmod {d_i}\)\(j\) 是有用的,因为之后的 \(d_i\) 都是现在 \(d_i\) 的倍数。

我们不妨考虑有用的 \(f_{i,j}\) 的级别。考虑每个位置上的最大可凑出面额其实也就 \(\sum\limits_{j=1}^i b_jd_j\),那么其中每 \(d_i\) 个才有一个有用的所以应当只有 \(\sum\limits_{j=1}^i \frac{b_jd_j}{d_i}\) 个。

直观感受告诉我们,这个东西应该不会太大。因为 \(d_i\)\(20\) 个就会增大一倍,所以一个面值至多 \(k\log b_i\) 步应该就不会再在求和里面贡献了,并且每次贡献也会按照 \(\log\) 的速度越来越少,两只 \(\log\) 会抵消所以应该是和 \(\sum b\) 直接相关。事实上数学推导可以得出所有位置上有用的 \(f_{i,j}\) 的数量的和是 \(2k\sum b\) 级别的,这是可以接受的。

所以我们直接做就好了。\(g_{i,j}\) 表示 \(f_{i,j\times d_i+m\bmod d_i}\),那么显然,有用的 \(j\) 就是一个前缀,直接枚举就可以。

还有一些比较 dirty 的细节问题:

考虑转移形如 \(f_{i,j\times d_i+m\bmod d_i}\to f_{i+1,k\times d_{i+1}+m\bmod {d_{i+1}}}\),那么数量限制形如:

\[k\times d_{i+1}-j\times d_i+m\bmod d_{i+1}-m\bmod d_i\le d_ib_{i+1} \]

事实上我们容易证明左侧总是被 \(d_i\) 整除的,\(k\times d_{i+1}\)\(j\times d_i\) 都是不需要证明的,取模部分打开取模可以发现 \(m\) 抵消掉,剩下 \(-pd_{i+1}+qd_i\),是可以被 \(d_i\) 整除的。

于是直接放心地移项并打开分数,考虑如何快速计算 \(\dfrac{m\bmod d_{i+1}-m\bmod d_i}{d_i}\)。不考虑使用高精度取模,我们继续打开取模,可以发现我们只需要快速计算 \(p,q\),也就是每一个 \(\left\lfloor\frac{m}{d_i}\right\rfloor\)。由于下取整具有除法结合律,我们不需要把所有 \(d_i\) 都乘出来,只要一个 \(a_i\) 一个 \(a_i\) 地除过去就可以了。然后这个数是可以直接存下来的,因为显然它小于 \(a_{i+1}\),特别地最后一步可能存不下来,这是无解判定。

由于 \(a_i=1\) 时可以直接继承,那么经过 \(\log m\) 次除法之后 \(p,q\) 都会变成 \(0\)。所以实现一个线性复杂度的高精除低精就可以了,我直接贺了题解的压位高精。

由于 \(d_n=\infty\),我们直接有答案是 \(f_{n,0}\),我们在 \(n-1\to n\) 处只转移 \(k=0\) 即可。

实现时需要动态维护一下每个位置上的有用上界来转移。转移的时候还需要前缀和优化。

注意有比较阴间的判定无解。上界小于 \(0\) 无解,除完所有 \(a_i\) 最后剩下的 \(m\) 过大也要无解。

实现需要想清楚一点。

CF1646E Power Board

被 2200 number theory + math 干爆了,加训数论!

我感觉这题相当 Hard 啊……打开 friends standing 一看发现 master 状态的 jjh 也不会……

首先我们有一个很自然的观察,我们考虑怎样的一行是一整行不重复的。可以发现,只要 \(i\) 不是任何一个数的幂次就整行都不会与前面的行重复。很容易用质因数分解 \(\gcd=1\) 证明。

然后我们就可以考虑把同底数的东西扔到一起了。我们把第 \(i\) 行扔到最小的 \(d\) 满足 \(d^k=i\) 的那个底数去考虑,因为一个数的所有幂次分解除了最小的那个都是由最小的那个产生出来的。

这样相当于在划分等价类,显然这些等价类是互不干扰的。然后我们考虑每个底数 \(d\) 分别怎么做(排除掉 \(d=1\) 的 corner case)。考虑这个等价类里面有 \(k=\log_dn\) 行,所以存在的数应该是 \(x\in [1,k],y\in[1,m]\) 的所有 \((d^x)^y\)。显然可以全部取对数,从而考虑有多少个不同的 \(xy\) 就行了。

这是简单的。由于 \(x\) 都在一个 \(\log\) 大小的前缀里,我们可以直接一行一行往里面加,加 \(\log\) 行,并且记录每个前缀有多少个不同的数就行了。注意到这些数最多不超过 \(m\log n\),也就是 \(2\times 10^7\) 的级别,所以直接开桶就行了。

不是,这真的很难啊!/ll

JOISC2015 D2T2 备用钥匙

我们容易想到把时间轴画出来,然后发现每段时间能否关门只与时间段两侧事件的人是否有钥匙、进门还是出门有关。考虑这两个人分别是 \(i,j\),不妨讨论一下所有的情况。

  • \(i\) 进门,\(j\) 进门。\(i\) 能关门当且仅当 \(j\) 有钥匙。
  • \(i\) 进门,\(j\) 出门。一定能关门。
  • \(i\) 出门,\(j\) 进门。\(i\) 能关门当且仅当 \(i,j\) 都有钥匙。
  • \(i\) 出门,\(j\) 出门。\(i\) 能关门当且仅当 \(i\) 有钥匙。

可以发现,我们的贡献形如某个人有钥匙就贡献,或者某两个人有钥匙就贡献。结合这个题浓重的 dp 味,我们可以自然想到,\(i\) 有钥匙就贡献的部分,就直接垒到 \(i\) 上,\(i,j\) 有钥匙就贡献的部分,应该给 \(i\to j\) 连边(很自然吧,按照时间顺序连边)之后放到边上。

然后我们发现这个图上每个点至多是一条入边一条出边,并且显然不可能存在环。所以就应该是一堆链了。我会 dp!

考虑显然的状态:\(f_{i,j,0/1}\) 表示前 \(i\) 个人有 \(j\) 把钥匙,第 \(i\) 个人有没有的最大贡献和。转移是比较显然的。

对于每条链跑出来这个之后背包一遍就可以了。

很简单的题。没做出来可能纯粹是因为时间不够了。

P6118 [JOI 2019 Final] 珍しい都市

题意:所有树点有一个颜色,计算距离某个点的距离独一无二的所有点中出现的颜色数量。要对所有点求解。

首先一个显然的结论,距离 \(i\) 独一无二的点来自于一条链,并且这条链一定包含最远的点。如果有多个距离它最远的点,任意一条都是满足的。

那么不妨考虑把直径找出来。

考虑点 \(i\) 距离最远的点一定来自于直径的一端。容易想到我们去统计某个端点对所有点的贡献,做两次就可以了。尤其注意到两边的答案具有覆盖性,也就是短一些的那一端答案总是 \(0\),所以我们可以不区分哪个是真正的最远的端点(不合法不优),直接两端都做取 \(\max\) 就可以了。

我们以一个端点为根,我们要对于每个点都快速统计出根到它的链上合法点的颜色数。考虑我们只要能维护所有这种点的添加删除就能类似莫队地实现数颜色。

不妨先长剖,这样每个点上深度最深的儿子成为重儿子。

考虑每个点上对到根的链产生最大影响的其实是重儿子。距离小于等于重儿子深度的点都会变成不合法的点,并且这种影响会延续到轻儿子中。也就是说,对轻儿子里的所有点,这些点也是不合法的,可以直接继承。

然后考虑重儿子,那就是距离小于等于最深轻儿子深度的点都会变成不合法的点了,重儿子里面的所有点也会继承这个。

此时你发现重儿子对不合法点的影响被轻儿子对不合法点的影响完全覆盖。所以说我们只要先做重儿子再做轻儿子,就可以很简单地维护合法点集,删掉的点不用再加回来了。

我们扫完之后就能计算这个点本身的贡献了。很容易证明子树中的点不会干涉到子树根的合法点,所以直接查就行了。

可以用一个 dfs 栈维护合法点集。考虑这个东西因为是按顺序的根到自己的一条链上的点集,应该是一个单调栈状物,从上至下距离越来越近,所以很容易维护,需要删点的的时候直接暴力弹栈就行了。

需要特别考虑一下这个点本身。这个点本身不受轻重儿子限制影响,所以需要特意加入,回溯的时候如果发现父亲在栈顶也要特意删掉。

每个点的进栈次数是度数次的,所以总时间复杂度 \(\mathcal O(n)\)

很精妙的题。

Code

LOJ#6020. 「from CommonAnts」寻找 LCT

similar to #6042.「雅礼集训 2017 Day7」跳蚤王国的宰相

一道乍一看没有什么思路的精妙题。为啥 LCA 这么强悍。

显然,自己连的边不会再删掉。所以删的都是原树边。

考虑操作的形态一定是,割掉一个子树把它直接接到根上,这是显然的。

考虑找出原树的重心,注意到任何一个大于一半的子树一定包含重心(如果不包含重心,那么它的大小一定小于等于重心在原来的任何一个子树,因为我们不会把两个子树接到一起)。

那么贪心地,我们要割肯定是割掉重心的前 \(k\) 大子树,使得最后包含重心的连通块尽可能小。可以注意到割掉的子树一定不产生坏影响,因为它们的大小一定都小于等于一半。

不妨以重心为根,考虑重心的每个子树里的点。考虑现在重心上挂的所有子树(特别地,这个点所在的那个子树只保留排除该点子树的剩余部分,也就是连到重心的那部分),我们需要做的就是删除其中最大的 \(k\) 个,查询其中剩下的部分的大小是否小于等于一半。

考虑怎么维护这个东西。为了直接干掉雅礼集训那个题,我们不妨直接计算至少要割掉多少条边才能使得它成为重心。直接拿权值线段树来维护每个子树的大小,每次在当前子树内的移动只会改变一个子树的大小,查询的时候在权值线段树上查询和的一半之前有多少个数就行了。

这样就做完了。

[AGC003F] Fraction of Fractal

疑似最水的 AGC F。这凭啥铜牌。

首先经过一些刻画和观察我们发现,如果一行上开头结尾都有 #,那么在下一层分形里面横向相邻的复制就一定会连接在一起。列上面也是同理的。

据此我们感受到一个很强的结论:只要一行一列上有这种东西它就一直是连在一起的。因为下一层上横向竖向都连起来了那就是一个连通块,并且显然同样的会存在这样的一行一列,归纳过去了。

所以这种情况下答案是 \(1\)

那么对于不存在任何一行也不存在任何一列上有这种东西你发现答案就是 \(c^{K-1}\),其中 \(c\) 是最初字符矩阵里面 # 的个数。因为这种情况下这个矩阵在后面的分形里面都是独立的。当然需要特判 \(K=0\)

接下来只需要考虑有且仅有行上有和有且仅有列上有了。这两个情况显然是等价的,我们下面只考虑行。可以发现,在有且仅有行上存在这种东西的时候,每一行之间是独立的,因为从第二层分形开始竖向就是连通不到一起的。

手玩几个可以发现,下一层合并连通块的问题事实上出在原本会有 \(c\) 个连通块产生,但是因为 ## 结构的存在会合并连通块。观察 \(K\) 逐渐增大时的答案序列也会发现 \(ans(K)\)\(c\cdot ans(K-1)\) 然后减去一个东西的。结合大得离谱的 \(K\),这促使我们思考线性递推然后矩阵快速幂。

你考虑这个东西本质上就是点减边容斥数连通块。点的部分是简单的,我们考虑边是怎么回事。

不妨假设最开始有 \(x\) 个行开头和结尾连通,有 \(y\)##。多手玩几个可以发现(事实上,已经可以直接找规律了):每一次分形,原矩阵中的 \(y\)## 都会导致合并连通块。不妨初始令 \(t\gets y\),从而 \(ans(K)\gets c\cdot ans(K-1)-t\),而那 \(x\) 行的两边的 # 因为每一个都是这层分形的复制,并且那 \(x\) 行之间都独立,从而下一次里面每个 ## 导致的合并的连通块数量会增大 \(x\) 倍,所以 \(t\gets t\cdot x\)

这个的原理也可以用点减边容斥来理解。你考虑我们每行独立之后我们事实上可以把整个图看成一棵树了(用一列把每一行穿起来的样子)。

这样就做完了。感觉就是个很朴素的观察题,有一个暴力观察结论是不难的,直接想也不是很难理解。可能是 OI 赛制导致的吧。

[JOISC2016 J] 危険なスケート

我真的不会做 JOI 题。

考虑这个东西很离谱,要我们搞一个最小值出来,但是最严重的问题就是前面的走法会造成新的冰块,后面可能会利用这些冰块,有后效性我们就不好操作了。

我会找性质!事实上,每个生成的冰块只会被使用至多一次。

Proof.
考虑“使用这些冰块”的意义是停在这些冰块的周围。注意到这意味着这个冰块和这个冰块旁边都是一个停留点。可以发现,要从一个位置挪动到一个位置旁边,只需要两步(往对应的墙边滑一次再滑回来)。这意味着如果我两次使用一个冰块,中间的那一次其实是不必要的,我可以用两步直接达到第二次,而显然用两次也至少需要两步,所以这是不劣的。
这其实对应了无负环最短路不会两次经过同一个点的性质。

由于每个冰块最多用一次,所以原地生成冰块的意义就是移动到相邻点。这意味着我们从一个位置挪动到相邻点的代价是 \(2\)。再加上从一个位置滑到原有的冰块边的代价是 \(1\),这就是我们全部的代价构成。把图建出来 Dij 一遍就行了。常数边权最短路可以多 queue 或者拆点做到 \(\mathcal O((n+m)V)\)

还有一个写的时候注意到的小问题:你考虑有一些 \(2\) 的连续路径是做不到的,因为之前的冰块会造成障碍,导致走不过去了。但是我们发现,这种情况可以直接走到对面墙再移动,这样做相当于这条连续的 \(2\) 被拆成前一半和后一半,前半从这头出发,后半从对面墙出发。

#####
#.#.#
#...#
#.#.#
#####

例如上图中从 \((3,2)\)\((3,4)\) 的正确走法并不是从 \((3,2)\) 向右挪两次,而是直接到对面墙再向左挪零次。

CF1733E Conveyor

冷静分析一下性质。

首先可以发现,每个史莱姆出现之后会走的步数都是 \(i+j\) 步。由于每个时刻只会生成一个史莱姆,所以根本不会存在史莱姆撞到一起,且 \(t\) 时刻棋盘上位于 \((i,j)\) 的史莱姆一定是 \(t-(i+j)\) 时生成的。

所以我们要询问的无非就是第 \(t-(x+y)\) 时生成的史莱姆在不在 \((x,y)\)

直接算考虑到其他史莱姆太神秘了所以不可能算出来,所以我们尝试计算经过 \((x,y)\) 的史莱姆数量在只考虑前 \(t-(x+y)\) 个史莱姆和只考虑前 \(t-(x+y)+1\) 个史莱姆(这里算上了第 \(0\) 时的史莱姆所以都要 \(+1\))有没有区别,差分一下看看就行了。

现在我们只需要关心这一堆史莱姆的整体行动就可以了。所以我们把所有史莱姆堆在一起,假设 \((i,j)\) 上有 \(c\) 个史莱姆曾经经过这里,由于每来一个史莱姆传送带就反转一次,所以应该恰好有 \(\left\lceil\frac{c}{2}\right\rceil\) 个史莱姆去了右边 \((i,j+1)\),剩下的都去了下面 \((i+1,j)\)。虽然可能在 \(t\) 时刻有一些史莱姆会停留在一些格子上,但是可以发现我们可以抛弃这个限制,直接让这些史莱姆走完。可以发现这并不会影响答案(这些史莱姆不可能影响 \(t\) 时刻后的 \((x,y)\))。

于是直接 dp 做出来就好了。时间复杂度 \(\mathcal O(qn^2)\)\(n=120\)

比较精妙的题。tourist 都不会咧 qwq。

P7967 [COCI2021-2022#2] Magneti

连续段 dp 板子。没有见过的东西。 [1st TransForm Cup] 一刻烟火。但是那个题要把所有的连续段信息全部记下来。

连续段 dp 解决的题目和插入 dp 很相似,都是一些东西的排列问题。它的主要思想是这样的:我们像插入 dp 一样确定一个插入顺序,插入 dp 中我们需要在插入的同时维护间隔的状态,而连续段 dp 我们只维护连续段状态。

  • 插入 dp:有若干种间隔,每次插在其中一种,由于插入顺序关系带来的美丽性质,可以继续维护下去。
  • 连续段 dp:有若干连续段,有顺序,但不独立维护每一个的信息。每次把相邻两个接起来,或者拼在首尾,或者新开一段。最后我们要求整个东西构成 \(1\) 个连续段就 ok 了。

现在来做这个题吧。

首先我们发现磁铁之间的空位是可以变得紧凑的,有一些磁铁之间空位实际上是不必要的(可以发现相邻两个磁铁的距离只需要 \(\max(r_i,r_{i+1})\),再大就产生可以删去的空位了)。从而我们可以先让磁铁紧凑地排在一起,然后任何把多的空位插回去的方案都是合法的。可以组合数算出。

于是现在我们只需要 dp 每种磁铁紧凑排列的长度的方案数就可以了。

可以发现,磁铁紧凑排列的长度的和就是相邻两项半径的 \(\max\) 的和。这促使我们想到先按照半径排序使得每次插入的贡献都产生在这个插入的数上,然后再用插入 dp 或连续段 dp 这种每次插入的 dp 手法。

  • 插入 dp:无法解决。因为我们不能存下目前排列所有的信息,由于每次插入会导致 \(\max\) 减少一个原来的,难以维护这个减少项。
  • 连续段 dp:可以解决。你发现连续段 dp 造成一个漂亮的事情就是不用删除贡献

由此还可以导出插入 dp 和连续段 dp 各自的优势。连续段 dp 维护求和贡献类的插入过程时可以实现贡献不删除,只增加信息(不影响原来的信息)的美好性质,减少需要维护的信息量;插入 dp 在插入过程可以轻松得知插入的合法性和方案数(从而可以不用维护一个连续段数量了)时可以降低复杂度。这体现在连续段 dp 通常都能完全覆盖插入 dp 完成的任务,但在一些性质优美的题目上复杂度表现不如插入 dp 优秀。

烟山嘉鸿 11-01 19:19:23
没有见过反例

回到这个题。考虑 \(f_{i,j,l}\) 表示插入了前 \(i\) 项,有 \(j\) 个连续段,紧凑的磁铁排列的长度是 \(l\) 的方案数。转移讨论一下合并相邻连续段、新增连续段、放在一个连续段首尾的方案数就可以了,不是很难。

[SEERC2021] C. Werewolves

首先应用众数杀手,我们枚举所有可能的绝对众数,标记 \(\pm 1\),然后考虑有多少个树上连通块的点权和是 \(>0\) 的。

根据树形背包的复杂度证明,对于 \(f_{i,j}\) 表示 \(i\) 子树内怎样怎样了 \(j\) 的子树合并类状态,只要对于任意一个节点 \(j\) 同时是 \(\mathcal O(siz_i)\)\(\mathcal O(m)\) 的,复杂度就是 \(\mathcal O(nm)\)这里有一个有若干显然笔误的复杂度证明。

从而我们考虑这个题只要我们每次能做到第二维在 \(\mathcal O(c)\sim \mathcal O(siz)\) 复杂度就全对了,因为 \(\mathcal O(\sum nc)=\mathcal O(n^2)\)

显然 \(f_{i,j}\) 表示达到 \(i\) 内和为 \(j\) 的连通块的方案数。考虑合并的时候,显然两边 \(-1\) 的个数不能超过 \(c\) 也不能超过 \(siz\)\(1\) 的个数同理,所以和的上下界区间就是 \(\min(2c,2siz)\) 大小的,是可以通过的。

更多思考:点减边容斥到底什么时候是有用的

通常来说,根据 2018 年 国家集训队论文,树上连通块问题只有两种办法:点减边容斥和树上 dp。

树上 dp 就是最基础的设 \(f_{i,\cdots}\) 表示以 \(i\) 为根的连通块,满足一大堆限制。由此可以获得树形背包、用线段树合并整体 dp 等,这里不再赘述。

点减边容斥之后得到的好处就是我们可以把一个直接树形 dp 困难的问题转化成计数有多少个连通块包含一个点。通常来说这个看上去是不弱的,但是对于一些特殊树连通块的交的问题后者就比前者更容易计算(比如连通块的交可以把外面的点随便选,因为并不需要让所有连通块之间满足某种严格的性质,只需要满足交在这个点且满足到这个点的性质就可以了)。

连通块的交问题参考 ytq 冠以 LCA 之名出的 P5291 [十二省联考 2019] 希望这题存在任何考场上做出来的可能性吗。

很精妙的数据结构题…… 但是感觉没什么思维瓶颈,可能是太累了脑袋动不起来……

考虑一个显然的想法,维护每个点后面第一个和它相加 \(=w\) 的位置 \(nxt_i\),从而我们只需查询 \(\min\limits_{i=l}^r\{nxt_i\}\) 即可。

然而平凡的想法单次修改会影响前面 \(\mathcal O(n)\)\(nxt\),不能直接 \(\text{polylog}\) 做。其实可以做到 \(\mathcal O(n\sqrt {n\log n})\),但是过不了。

我们考虑 \(nxt\) 里面很多都是没用的。具体来说如果 \((i,nxt_i]\) 之间存在 \(j\) 满足 \(a_j=a_i\)\(i\) 显然都是没用的。因为这些合法配对包含了其他合法配对,只需要能数到包含的就足够了。

有了这个,再维护 \(nxt\) 就不难了。我们每次只需修改四个数的前驱和这个位置本身就可以。

CF1848F Vika and Wiki

神级 *2400。。。

考虑所有数全部变成 \(0\) 之后显然有单调性,所以可以二分或者倍增。

观察可以发现对于任何一个 \(\{a_n\}\) 经过 \(2^{k}\) 次操作之后 \(a_{i}\) 会变成 \(a_i\oplus a_{(i+2^k)\bmod n}\)。这同时说明了答案上界是 \(n\),并且一定有解。

我们直接从高位到低位倍增步数,考虑一个位上取 \(1\) 后的步数是否合法。实现细节上倍增最大的不合法步数是更容易的。

CF527E Data Center Drama

邪恶欧拉回路。

加边显然需要把所有点的度数都搞成偶的,这可以把奇度点(必然有偶数个)两两配对实现。总边数显然也需要是偶的,这可以添加自环实现。这是答案下界,我们可以猜测有这两条之后就一定可以构造了。

进一步考虑这个条件,一眼就想到是把这个图看成无向图存在欧拉回路。考虑猛攻这个方向,怎么在欧拉回路上做文章。

首先一个想法是把整张图凑成每个点的度数 \(\bmod 4=0\),这样直接欧拉回路,出入边就自动变成两半,每半都是偶数。但是这样还需要加边,就炸了。

我们发现直接欧拉回路不行的原因就是一个点上可能有偶数度,但是分成两半会变成两半奇数。可以发现,我们此时只需要反转一条边就好了,但是问题是要递归反转下去,事情开始复杂起来。事实上这也是可以做的,我们只要反到另一个不好的点就可以了,在生成树上找一条路径反转数据结构维护就行。显然这种出入度均为奇数的点必然有偶数个(考虑总边数等于所有点的入度和或出度和),这么匹配可以消完。

这样很 naive,我们考虑欧拉智慧。

我们充分发扬人类智慧,看看怎么在欧拉回路上做文章。我们希望进行一些反转使得欧拉回路满足条件。可以发现,由于每个点都会被进入一次出去一次,所以我们只需要在进来和出去当中选恰好一边转成反方向就能两进两出 sxh,所以我们直接给欧拉回路黑白染色,反转一种颜色就对了!

好天才啊。

CF150D Mission Impassable

为啥我这么菜啊???感觉这题就是个很 naive 的 *2400 不到的题啊。还是要想清楚。。

upd:发现自己犯蠢的原因了:有个地方搞错了,删掉长度为 \(2\) 的子串也需要中间删空的……还有就是区间 dp 是可以不管顺序的,两边的事情可以看成是一边一边,然后推到中间的状态,减少枚举……

容易想到区间 dp,但是随便一搞就会搞出来一大堆状态,变成屎山,可能也能做吧,我想了个四个状态的。。。

考虑这个东西的答案形态是一堆删空的区间拼在一起,所以首先我们容易想到 \(f_{l,r}\) 表示删空区间 \([l,r]\) 的最大贡献,最后再用一个简单的 dp 把不交的删空区间的最大贡献和 dp 出来。然后每次我们要从中提取一个回文串出来删掉,所以不妨枚举回文串的最后两个端点然后事情就麻烦起来了……

转移限制困难不妨使用状态限制。

\(g_{l,r,k}\) 表示删空区间 \([l,r,k]\) 的其他部分,保留一个长度为 \(k\) 回文串的最大贡献。那么 \(f\) 的转移就很显然了。

考虑 \(g\) 的转移。首先两端可以加上一些删空的段,所以我们直接 \(g_{l,r,k}\gets \max\limits_{c=l}^rf_{l,c-1}+g_{c+1,r,k}\),另外一边同理。剩下的就是直接在两边填两个字符了,所以直接从 \(g_{l+1,r-1}\) 转移过来(当然需要特判 \(l=r\))。

需要特别转移一下 \(g_{l,r,0}\),本质就是删空。

啊啊啊啊啊啊真的好唐。

P6228 [BalticOI 2019 Day2] 汤姆的餐厅

在 SAR 里面薅思维瓶颈了。感觉思维瓶颈应该是在想到确定合法厨师子集之后浪费时间一定的那个地方。

考场上做出的部分:

考虑每个菜都需要至少 \(k\) 个厨师,那么显然我们只需要找 \(k\) 个厨师过来在这个菜上花费 \(1\) 的时间,剩下的就随意了。

从而我们发现时间分为两个部分:一部分是 \(n\times k\) 的每个人在菜上浪费 \(1\) 时间的过程,这部分每个厨师最多单次只能贡献 \(\min(b_i,n)\),因为最多只能把所有菜都走一遍;另一部分就是任意安排,只要最后有 \(\sum a-n\times k\) 的时间分了就行。

然后就不会了。直接 dp 这俩玩意是 \(\mathcal O(n^9)\) 的。

考虑优化,可以发现上面那个 \(\mathcal O(n^9)\) dp 比较麻烦的是还要枚举厨师的时间怎么分配。贪心地想,一个厨师一定会把自己尽可能多的时间放到那 \(n\times k\) 的时间里。因为考虑我们的要求仅仅是选择一个厨师子集 \(S\) 让这个子集的厨师 \(n\times k\) 那部分能够大于 \(n\times k\),浪费的总时间其实是一定的 \(\sum a-\sum\limits_{i\in S} b_i\),所以我们根本不关心 \(n\times k\) 部分以外的问题,直接把那里面尽可能塞满就可以了。

所以我们直接 \(f_{i,j}\) 表示前 \(i\) 个厨师,总 \(\sum b=j\)\(n\times k\) 的那部分能获得的最大贡献。直接做就三方了。

P2764 最小路径覆盖问题

形如“每个……恰有一个……”这样的问题,可以考虑二分图匹配。因为二分图匹配中每个匹配中的点都恰属于一个匹配。对于常数个也可以如法炮制。

考虑 DAG 最小路径覆盖,显然应该网络流。按照路径的套路,我们应该把一个点拆成作为边起点和边终点,这样就能转化成二分图匹配:每个点只能做至多一次起点做至多一次终点。

我们考虑所有起点全部与 \(S\) 相连,所有终点全部与 \(T\) 相连,一条边 \(u\to v\) 就把 \(u\) 做起点向 \(v\) 做终点连边。显然这些边的容量都应该是 \(1\),因为每个点都只能用一次。

考虑这样算出来的最大流是每条路径上有多少个点做终点,也就是边数,需要用 \(n\) 减去才是路径数。不用 \(m\) 减是因为存在无用的边(比如两边一边是路径端点一边是路径中间的那种边)。

CF1913E Matrix Problem

我真是一点不会网络流。。。。。。。


显然是网络流。

考虑我们怎么表示一个点 \((i,j)\) 被选了,容易想到应该费用流一下,流过一条边表示选择一个点翻转,费用为 \(1\)

考虑这产生的影响,就是 \(a_i\gets a_i\pm 1,b_j\gets b_j\pm 1\)。容易想到需要建两排点表示每一行、每一列。此时我们不能把两类点都往汇点上连,否则一个点的贡献需要 \(2\) 的满流才能表示,这样的要求不符合网络流的性质。所以我们应该把一种限制拆到源点上,表现为限制流量为 \(a_i\)\(b_i\)

不妨把行限制拆到源点上,源点 \(S\)\(i\) 连容量为 \(a_i\),费用为 \(0\) 的边表示一行需要 \(a_i\)\(1\)\(i'\) 向汇点 \(T\) 连容量为 \(b_i\),费用为 \(0\) 的边表示一列需要 \(b_i\)\(1\)。这样在判掉 \(\sum a\ne \sum b\) 的无解后,只要满流 \(\sum a=\sum b\) 就意味着合法解。

考虑怎么连接 \(i,i'\)。显然对于一个点 \((i,j)\),我们应当连接 \(i\to j'\),流这条边,也就是选这个点,就可以让源点到 \(i\)\(j'\) 到汇点的流量都 \(+1\),表示这一行一列多了一个 \(1\)

问题是费用。这样做我们不好计算翻转一次 \(1\to 0\) 的费用。不妨先把所有 \(1\) 翻过来,这样 \(1\to 0\) 费用就为 \(0\),但 \(1\to 1\) 就需要费用 \(-1\) 表示翻回去。这样跑费用流,最后给最小费用加一个 \(\sum c\) 就可以了。

实现时注意判无解需要判 \(\sum a=\sum b\)、判满流和判费用负圈。

int n,m;
int c[55][55];
int a[55],b[55];
int S,T;
int head[N],nxt[N],to[N],wgt[N],cst[N],flow[N];
int dth[N],dep[N],now[N];
int cnt[N];
bool vis[N];
int tot=1;
int cost;
int suma,sumb,sumc;
void add(int u,int v,int w,int c){
    tot++;
    nxt[tot]=head[u];
    head[u]=tot;
    to[tot]=v,wgt[tot]=w,cst[tot]=c;
    tot++;
    nxt[tot]=head[v];
    head[v]=tot;
    to[tot]=u,wgt[tot]=0,cst[tot]=-c;
}
bool spfa(){
    fr1(i,S,T) dth[i]=0,dep[i]=1e9,cnt[i]=0;
    dep[S]=0,dth[S]=1;
    queue <int> q;
    q.push(S);
    vis[S]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        vis[x]=0;
        if(cnt[x]==n) cout<<"-1\n",exit(0);
        for(int i=head[x];~i;i=nxt[i]){
            int y=to[i];
            if(flow[i]<wgt[i]&&dep[y]>dep[x]+cst[i]){
                dth[y]=dth[x]+1;
                dep[y]=dep[x]+cst[i];
                if(!vis[y]) q.push(y),vis[y]=1,cnt[y]++;
            }
        }
    }
    return dep[T]!=1e9;
}
int dfs(int x,int fl){
    if(x==T) return fl;
    vis[x]=1;
    int used=0;
    for(int i=now[x];~i;i=nxt[i]){
        now[x]=i;
        int y=to[i];
        if(dth[y]==dth[x]+1&&!vis[y]&&dep[y]==dep[x]+cst[i]&&flow[i]<wgt[i]){
            int d=dfs(y,min(fl-used,wgt[i]-flow[i]));
            if(d){
                used+=d;
                cost+=d*cst[i];
                flow[i]+=d;
                flow[i^1]-=d;
            }
        }
    }
    vis[x]=0;
    return used;
}
int main(){
#ifdef Shun
    freopen(".in","r",stdin);
    freopen(".out","w",stdout);
#endif
    memset(head,-1,sizeof head);
    ios::sync_with_stdio(false);
    cin>>n>>m;
    fr1(i,1,n) fr1(j,1,m) cin>>c[i][j],sumc+=c[i][j];
    fr1(i,1,n) cin>>a[i],suma+=a[i];
    fr1(i,1,m) cin>>b[i],sumb+=b[i];
    if(suma!=sumb) return cout<<"-1\n",0;
    S=0,T=n+m+1;
    fr1(i,1,n) add(S,i,a[i],0);
    fr1(i,1,n){
        fr1(j,1,m){
            if(c[i][j]) add(i,n+j,1,-1);
            else add(i,n+j,1,1);
        }
    }
    fr1(i,1,m) add(n+i,T,b[i],0);
    int ans=0;
    while(spfa()){
        fr1(i,S,T) now[i]=head[i];
        ans+=dfs(S,inf);
    }
    if(ans!=suma) return cout<<"-1\n",0;
    cout<<cost+sumc<<'\n';
    ET;
}
//ALL FOR Zhang Junhao.

[JOI 2022 Final] 沙堡 2 (Sandcastle 2)

首先肯定要会判定再谈数数。

我们发现因为限制过于强悍,判定是简单的。我们必然是从最大的那个点开始走,每次我们一定走所有数中小于自己的最大值,当出现走到重复或者走不完的情况时就不合法了,如果能恰好走完整个矩形就合法。这启发我们直接连边转到图论上做,那么我们只需数有多少个矩形包含一条哈密顿通路。

考虑我们的图的性质。每个点一定有至多 \(1\) 的出度,而整个图不可能有环,所以任取一个点集都构成一棵有向树。那么哈密顿通路这个条件就等价于恰好一个点入度为 \(0\)。此时我们存在一个做法就是求矩形里面的入度数量。

考虑给原矩形连边,那么在考虑子矩形的时候其实中间的都会直接用原矩形的边,只有边界上的会有问题。考虑我们如果扫描线地维护,那么可以发现边界第一层的格子和边界第二层的格子可能会因为边界的移动产生入度的变化,比如 JOI 题解里面的这张图:

边界往左移,可以看到之所以我们要维护边界内第二层的点就是因为边界向左移动导致入度改变。

那么正解就容易了,我们直接强行维护第一层第二层和更内层,边角当然也要分开维护,\(25\) 个前缀和就好了。

这太夸张了!有没有更好写更不恶心人的做法?

考虑直接判定哈密顿通路。我们充分发扬人类智慧,我们刚刚已经发现这个图是一个树,而我们只需判定它是不是链,有向链区别于有向树的特征就是每个点只被至多一条边指到,所以我们考虑对于每条边和它的到达点绑在一起,如果我们能把边的贡献和点的贡献完全抵消掉(除了链尾),那就是成了。

考虑一个和哈希或者异或哈希,反正能抵消掉就行。具体来说,我们对一个点加上下一个点的贡献,在一个点又减去本身的贡献,那么哈密顿通路上所有的点都会因为是上一个点的下一个点,本身又会被走到而被抵消到只剩第一个点。

所以直接构造哈希函数 \(H(x)=a_{ij}-to_{ij}\),其中 \(to_{ij}\) 表示能走到的点的权值,如果没有能走的则为 \(0\)。这样之后一个矩阵合法意味着整个矩阵都将抵消到只剩下一个数(也就是只有一条路径)。判定恰好一个数是方便的,我们把可能的起点(四周都比自己小的点)的 \(a_{ij}\) 都设置为一个极大值,这样我们直接判定整个矩阵的和是不是这个极大值即可。

此时 \(\mathcal O(H^2W^2)\) 已经容易了。我们只需扫描线过去,提前预处理维护一下每个点作为左边界点、右边界点、中间点指向的位置,对权值累前缀和即可。当然需要特判长宽为 \(1\) 的特殊处理一下。

P9197 [JOI Open 2016] 摩天大楼

我真的不会费用提前计算,你让让我吧。

显然连续段 dp,为了去掉绝对值符号就从小到大加。

考虑每次合并两个连续段,那么会出现的问题是要计算类似 \(2a_i-a_{i-1}-a_{i+1}\),把所有连续段两端的 \(a\) 全部记下来复杂度就爆炸了,考虑费用提前计算。

然后我们考虑把所有连续上升下降的合在一起,然后特别维护一下

P8340 [AHOI2022] 山河重整

为什么 EI 是神!

我们需要知道一个结论:一个集合能凑出 \([1,n]\) 的所有数当且仅当对于所有 \(i\in[1,n]\),所有 \(\le i\) 的和 \(\ge i\)。你考虑这个是比较显然的,因为如果存在一个不满足的 \(i\),那么 \(i\) 无法被所有 \(\le i\) 的数凑出,大的数又没用,它就死了。

然后这题容易写出一个 dp。\(f_{i,j}\) 表示前 \(i\) 个数能凑出极长的 \([1,j]\) 前缀的方案数。每次转移一个更大的数可以凑出 \([1,j+i]\)(当然,需要 \(i\le j+1\),否则 \(j+1\) 的坑还在),\(j+i+1\) 显然仍然无法凑出。

这样可以获得 \(60\) 分,问题是根本无法优化。

考虑 dp 的这个理解方式写出来之后,本质和那个结论是一样的,就是要求所有 \(\le i\) 的数的和 \(\ge i\)

要求所有!一眼容斥。

考虑第一个无法被 \(\le i\) 的数凑出的 \(i\),也就是第一个 \(\le i\) 的数的和 \(<i\)\(i\)。这样的好处是我们确定了所有 \(\le i-1\) 的数肯定是 \(i-1\)(容易反证)。

这让你想到一个题:AT_dp_y,就是那个不经过障碍网格图路径计数的题。我们考虑 dp \(f_i\) 表示只考虑 \([1,i]\) 的数,\(i+1\) 是第一个炸掉的点的方案数,那么考虑类似那个题地容斥这个东西,考虑前 \(i\) 个凑出来和为 \(i\) 的全部方案,扣掉中间炸掉的方案。

考虑枚举第一个炸掉的点 \(j\),那个点前面直接用 \(f_{j-1}\),后面除了 \(j\) 必不选,需要在 \((j,i]\) 当中凑出 \(i-j+1\) 去补足 \(j\) 前面凑出的 \(j-1\)

于是:

\[f_i\gets g_i-\sum\limits_{j=1}^{i-1} f_{j-1}\times c(j+1,i,i-j+1) \]

其中 \(g_i\) 表示 \([1,i]\) 凑出和为 \(i\) 的方案数(因为炸掉的是 \(i+1\)),\(c(j+1,i,i-j+1)\) 表示用 \([j+1,i]\) 中的数凑出 \(i-j+1\) 的方案数。

注意答案不是 \(f_n\),而是所有方案减去 \(\sum f_{1,2,\cdots,n-1}\times 2^{?}\),也很好理解吧。

边界是 \(f_0=f_1=1\)

先看怎么转移 \(g\) 这个东西。

注意到这个东西弱于背包的性质:因为元素是互异的,所以至多选择二倍根号个就会达到 \(n\);其次我们可以减少一下自由度,因为凑出 \(i\) 显然用不到 \(\ge i+1\) 的数,所以可以直接考虑 \([1,n]\) 有多少种凑出 \(i\) 的方法。

我们充分发扬人类智慧,把这个东西转成 \(i=\sum 1\) 然后交换求和顺序!换句话说,考虑 \(\ge i\) 的选择了几个,再求和,也是一样的。

考虑这个“\(\ge i\) 的有多少个”对于所有 \(i\) 的限制:

  • \(\ge i\) 的数的数量最多比 \(\ge i-1\) 的数的数量少 \(1\),因为每个数都是互异的,最多只有一个 \(i-1\)
  • 最小的数,\(\ge\) 它的不能超过二倍根号个,不然所有数的和一定会超过 \(n\)

然后我们发现这个转化最好用的点:计数所有合法的“\(\ge i\) 的有多少个”方案和计数我们原来的背包是一样的,因为这俩是一一对应的。

献祭一张题解图片:

image

这表示 \(\{8,7,6,4,3,1\}\) 这种背包,那么我们转成了用 \(\ge i\) 刻画,本质就是从竖着看转成横着看了,对应成 \(\{6,5,5,4,3,3,2,1\}\)。换句话说,我们只要统计后者的数量,排序后就能一一对应回前者。

考虑怎么数合法的集合的数量。转化那两条限制,也就是从最大的数开始必须到 \(1\) 的每个数都出现,以及最大的数不能超过二倍根号。使用对二倍根号及以下的数做完全背包可以轻易计数这个东西。

现在我们把 \(g\) 做完了。

你妈的,这段写了两次都因为 sb 博客园没保存,不写了,反正就是发现每次 \(f\) 只依赖前面一半的 \(f\),所以 cdq 分治折半下去计算,转移的时候类似上面优化转移一下(首先枚举一下 \([j+2,i]\) 里面选了多少个数,这个是小于等于二倍根号的,把这个 \(f\) 转移过去之后,发现限制和之前是一样的,所以还是同样对这个玩意做一样的完全背包。基本上就相当于带权完全背包了,每个方案的贡献都有对应的 \(f\))。

时间复杂度 \(\mathcal T(n)=\mathcal T(\frac{n}{2})+\mathcal O(n\sqrt n)\),根据主定理就是 \(\mathcal O(n\sqrt n)\) 的。

压着《青春期》的韵脚把这道战线长达三天的题处理掉了。

posted @ 2024-09-25 13:10  Shunpower  阅读(29)  评论(0)    收藏  举报