Hey Gift:别在意争斗 请看到最后 we don't wanna toe to toe yeah

做题记录限时返场。

CF277E Binary Tree on Plane

我去,为什么我不会网络流。

看到 \(n\leq 400\),首先考虑区间 dp,然后完全不会。

考虑网络流!考虑怎样让一个流对应一种二叉树,然后跑费用流就很合理。很明显,一个二叉树可以被父亲-儿子关系唯一确定,所以我们考虑一个流可以代表所有的父亲-儿子关系,这样映射树很方便。

考虑一个点作为父亲,可以给到最多两个儿子,所以直接从源点通入 \(2\) 的流量,然后向所有可以成为儿子的点建一条 \(1\) 容量有代价的边,再从那些儿子连一条 \(1\) 容量的边表示只有一个父亲去汇点就行了。可以发现需要拆一下点,把做儿子的和做父亲的分开搞。具体来说,假设做父亲的是 \(u_1\),做儿子的是 \(u_2\)\(S\to u_1\) 连接 \((2,0)\)\(u_1\to v_2\) 连接 \((1,w)\)\(v_2\to T\) 连接 \((1,0)\)

跑一遍费用流完事。如果最大流不是 \(n-1\) 说明有儿子没父亲,这就寄了,输出 \(-1\)

CF506D Mr. Kitayuta's Colorful Graph

神级根号分治!

考虑转化题目,本质上就是把每一种颜色的边单独看之后,查询 \((u,v)\) 在同个连通块里的颜色数量。

那么对于每种颜色就有两种暴力:如果点很少,直接 \(\mathcal O(n^2)\) 枚举每个连通块的所有点对贡献答案;或者 \(\mathcal O(q)\) 枚举所有询问,直接查询是否在同个连通块内。

考虑一个事实:如果一种颜色边的数量 \(<\sqrt m\),那么所有连通块点数的平方和是 \(\mathcal O(n\sqrt n)\) 的,那么我们可以直接做第一种暴力。而边数 \(>\sqrt m\) 的颜色不会超过 \(\sqrt m\) 种,就可以做第二种暴力。

CF1863E Speedrun

vp 的时候搞难受了。为什么两个性质都注意到了也没做出来?

显然建立 DAG,然后考虑把所有源点都放在一天之内启动算出每个点的最晚到达时间,这样就能简单 dp 搞出来每个点的最早完成时间 \(t\),然后可以得到每个连通块的结束时刻。然后排一下序依次启动一下就好了。

然后你发现这是错的,因为有可能第二天再启动会更优。然而并不是一个连通块整体都第二天启动最优,可能是一些点第二天启动一些点第一天启动更好。(test 2 的惨痛教训)

还可以发现,把中间的点往后移没什么用,它只关心前面的点什么时候到达,能早走就早走,乱移还可能变大答案,所以我们只考虑挪动源点。

有一个显著的性质:放到第二天的源点一定是一个前缀。感性理解就是这样我们可能可以在右移开始时间的情况下调整结束时间,如果前缀里面做得很快的话就可能会更优。证明可以考虑如果不是一个前缀是什么情况:存在一个位置满足它移到第二天而前面不移,如果这样更优的话把这个位置放到第一天显然不劣,所以性质是对的。

还很显然的是,没有任何源点会放到第三天甚至以后会更优。源点最多移一天也表明无论怎么移动所有点都最多向后移一天,换句话说,它就只有两种可能,要么在所有点都不移的情况下最快的那一天也就是 \(t_i\) 的那一天启动,否则就在下一天启动。

所以我们考虑把所有源点先按照 \(h\) 排序,先算一个所有源点都在第一天的答案,然后依次移动。非常聪明地考虑记忆化搜索地移动,在每个点上打一个标记叫做是否移动过,然后考虑 toposort 地只更新一个点可以到达的所有仍然没有移动的的点,显然是 \(\mathcal O(n)\) 的。

具体来说,每一次移动一个源点到后一天时,我们把它扔进队列跑 toposort,如果它连到的点还没移动过但是时间变大了就更新过去,并把连到的那个点进队,容易发现这个点必然会挪到第二天去,所以每次碰到的点都可以打上标记。由于挪移过程中最后一个任务的结束时间单调不递减,容易维护,而第一个任务的启动时间显然就是枚举位置的下一个位置。

这里其实还有个问题:如果一个点连到的点在挪移了这个点之后碰到的时间会向后,但它已经挪移过了,我们不会更新它,似乎 \(t\) 的维护就错了,然而你发现这种情况根本不可能(讨论一下就能证明)。

然后直接模拟就行了,其实好像不太难写。。。

over!

CF914E Palindromes in a Tree

读错题了。。我以为要直接是回文串。。。原来是要数路径上可以排出来回文串的方法数。。。

首先注意到字母集只有 at,敏锐地发现只有 \(20\) 个字母。

然后一个垃圾转化,一个字符集可以排成回文串当且仅当最多一个字母出现了奇数次。

看到数通过端点的路径数量还 \(2\times 10^5\) 直接点分治,问题在于怎么数有多少个经过端点的路径满足要求。由于只有 \(20\) 个字母,不难想到状压,每一位上的 \(1/0\) 表示从目前点分治的根到这个位置,这个字符的出现次数是奇是偶。合并操作直接异或就行。假设状压数组是 \(d\)

这样的话相当于数有多少个 \(d_i\oplus d_j\oplus d_{rt}=0\lor 2^k(0\leq k<20)\),不过 \(i,j\) 显然要在 \(rt\) 的两颗子树里。

条件有点恶心。考虑先枚举 \(i\),这样容易得到 \(d_j\) 希望是什么,在整棵树里瞎几把数一波,然后抹掉同一子树那部分的贡献。这容易做到 \(\mathcal O(n\log n)\),当然有精妙的线性做法。

然后处理一下细节就弄完了。相同的贡献要抹掉,但是从根出发的不能抹,所以有点小恶心。

CF1954E Chain Reaction

为什么曹博士和洪队长会这个……思维爷。

注意到操作次数最小只是套皮,操作次数肯定是定值。每死掉一只怪物就会把整个东西裂成两半,然后每个区间分别接着干就行了。

敏锐地发现,在区间裂开之后,我们不应该像 DFS 一样考虑从左到右一个一个区间地完全干掉一个之后再做下一个,因为这样每层都等价于原问题,不好统计需要的刀数。而是考虑像 BFS 一样把它再削掉一层,也就是所有区间统统挨一刀进入下一个状态,这样每削掉一层需要的刀数就是还活着的怪物组成的连续段数量,看起来就非常美!

可以注意到,这样的话我们只需要削 \(\frac{\max}{k}\) 层,对于 \(k:1\to \max\),求连续段数量的次数是调和级数级别的!所以我们只需要考虑怎么求出有多少个活着怪物的连续段就行了。

没必要在线维护杀怪物。可以发现我们只需要求出有多少个生命值大于 \(kx\) 的怪物组成的连续段。直接从大到小扫一遍怪物合并连续段,记录答案就行了。

CF268D Wall Bar

tricky dp。

先搞明白题意:要求必须在 \(n-h+1\)\(n\) 层的横杆中能够爬到一个就行了。。。

注意到只有四个方向,考虑把四个方向塞进 dp 状态。

容易设计 \(f_{i,j,k,l,p}\) 表示当前在第 \(i\) 层,四个面分别做到了 \(j,k,l,p\) 层的方案数,这样的话转移也很方便。

但是这个显然过不了,考虑第一个优化。注意到 \(h\leq 30\),如果某一个方向出现了相邻两个的差超过 \(30\) 那么再大就没有用了,所以我们维护四个面分别做到了 \(i-j,i-k,i-l,i-p\) 层的方案数。这样就存在 \(j,k,l,p\leq h+1\)\(h+1\) 表示的就是一切爬不到的情况。注意某个方向出现了爬不到的情况以后这个方向上都表现为爬不到了。

然而还是过不了。容易发现 \(j,k,l,p\) 中必然有一个是 \(0\) 或者 \(h+1\),显然是第 \(i\) 层放置的那个方向。所以我们改一维为 \(0/1\),剩下三维表示剩下三个方向的情况,考虑下一层放同向还是非同向就可以轻易转移了。

有点菜啊不会这个。APIO 咋搞。

CF1399F Yet Another Segments Subset

我去,更菜了。这个是傻叉东西。

首先很容易想到如果可以知道每个区间 \(i\) 里面最多能包含多少个线段 \(g_i\) 的话,那么把 \(g\) 看成权值,最外层就是一个选不相交区间总权重最大的傻逼 dp。

考虑怎么 dp 出来 \(g\),很容易发现里面和外面是一样的东西啊,而且每个线段的权值仍然是 \(g\),只是说对于线段 \(i\),我们只想在 \([l_i,r_i]\) 之间做这个事情。

由于支持 \(\mathcal O(n^2)\) 复杂度,事情就很简单了。你可以选择以位置为下标 dp,设 \(dp_x\) 表示 \([l_i,x]\) 位置不相交区间的最大 \(g\) 和。只需要在每个区间的右端点都把区间挂上去,然后转移:

\[dp_{r}\gets \max\{dp_{r-1},dp_{l_j-1}+g_j(r_j=r\land l_j\geq l_i)\} \]

答案 \(g_i\) 就是 \(\max\limits_{j=l_i}^{r_i} dp_j\)

也可以选择以区间为下标 dp,只需要把 \([l_i,r_i]\) 内的所有区间都拿出来排序再 dp 就行了。

实在太傻叉了。想不清楚啊。

CF509F Progress Monitoring

dp 题单一直在看题解,怎么回事呢?

很容易发现就是把这个树的 DFS 序扔出来了。那么我们很容易由子树区间化得到一个转化:对于每个子树,它在这个序列上都占了一个区间 \([l,r]\),而这个区间的长相应该是 \(l\) 那个位置是自己本身,\([l+1,r]\) 分成若干块,每一块的第一个数单调递增(由题目中的 DFS 方式决定的这个限制),然后每一块内都是儿子子树并以此类推。

那么我考虑了一个傻叉区间 dp,\(f_{l,r}\) 表示 \([l,r]\)\(l\) 为根的树的数量,然而转移的时候要先枚举最小那个子树占的部分(假设是 \([l+1,k]\)),再在 \([k+1,r]\) 取所有第一个数单调递增的切分方案表示儿子,而贡献还是乘起来,这不太好搞(不过好像也能强行搞)。

这个时候非常敏锐地注意到!我们的后半部分其实就是 \(f_{k,r}\)!可以理解成把 \(k\) 为根的树删掉根,儿子全部扔上去,很明显这和把 \([k+1,r]\) 划分的总贡献是相等的(每一个这样的划分加个 \(k\) 就成了 \(f_{k,r}\))。而第一个数单调递增的要求,很明显 \(f_{k,r}\) 的划分内部肯定符合这个要求,且 \(b_{k+1}\) 应该是所有儿子里面最小的第一个数,所以钦定 \(b_{l+1}<b_{k+1}\) 才能转移 \(f_{l,r}\gets f_{l,r}+f_{l,k}\times f_{k,r}\) 就行了。

不是,这么难。。。

ARC176A 01 Matrix Again

我吐了。。这什么东西。

ARC176B Simple Math 4

傻逼题为什么不会?

注意到想求 \(2^N\equiv r\pmod{2^M-2^K}\)\(r\) 的个位数,那么考虑乱写这个同余方程变成 \(2^N\equiv 2^N-2^{N-M}(2^M-2^K)\pmod{2^M-2^K}\),然后得到 \(2^N\equiv 2^{N-(M-K)}\pmod{2^M-2^K}\)

由于 \(M>K\),所以可以把 \(N\) 一直往小了替换,反正最后换到 \(N<M\) 的时候就成傻逼题了,因为 \(2^M-2^K<2^{M-1}(K<M-1)\)。记得特判 \(K=M-1\)

CF1914F Programming Competition

*1900 greedy 又一次断送了我的 CM。

注意到一个子树配完之后,剩余的点到它的父亲上就没有区别了,所以我们考虑从下往上 dp 或者贪心。

很容易想到主元素相关的那堆性质:如果没有主元素就可以互相匹配完,否则全部都可以去配主元素。这启发我们考虑每一个子树,如果该子树的所有儿子可以互相配完,那就让它们配完,但可以发现,如果是后者的情况,都去配主元素之后,主元素那个子树内部可能剩了一堆点,这些点可以进行调整,因为其他点又不关心匹配到这个子树里哪个点,这样可以增加一些这个子树内的匹配。

那么我们这时候希望这个子树内先配一波,然后再留给剩下的子树用。所以我们考虑这个子树的内部匹配数,如果余下的点不够剩下子树的和,就保留到余下的点够用,可以发现此时答案就是顶满的(子树内所有点都能配完)。否则就是先全部配完,再让其他子树的点都用上,此时显然最优。

所以直接设 \(f_x\) 表示 \(x\) 子树的最大配对数就可以轻易从下向上做了。

CF819A Mister B and Boring Game

明天再看,今天累了。

P7600 [APIO2021] 封闭道路

首先我们容易得到一个 \(36\) 分的树上 dp。

\(f_{i,0}\) 表示 \(i\) 子树内部做完,可以不允许 \(fa_i\to i\) 的边保留的最小答案;\(f_{i,1}\) 表示 \(i\) 子树内部做完,允许 \(fa_i\to i\) 的边保留的最小答案。那么转移时,先假设全部都选 \(f_{son,1}\),然后考虑替换造成的代价 \(f_{son,0}-f_{son,1}+w\)。显然我们选择最小的一些代价来替换。对于 \(f_{i,0}\),需要替换到所剩儿子数量 \(\leq k\) 为止,对于 \(f_{i,1}\),需要替换到所剩儿子数量 \(<k\) 为止,可以发现这个替换过程大概是和度数 \(-k\) 相关的。

注意替换代价可能有负数,负数肯定要取完。

考虑优化这个 dp。可以发现,当 \(k\) 逐渐变大的时候,一些点上面可以选择全部的儿子,而且以后都是这样了,但是我们每次还要排序然后选,简直像 nt。所以我们考虑不要这个过程。

我们把所有点按照度数从小到大排序,这样随着 \(k\) 增大,不再有用的点会构成一段前缀,我们可以使用双指针维护无用点集合。如果我们可以让无用点在无用后不再被 dp 过程用到的话,那么每个点被 dp 用到的次数就是它的度数,由于度数和为线性,复杂度就是对的。

注意这里对时间复杂度要求很苛刻。无用点消失之后不能在过程中被再次扫到或者读取(哪怕不做任何操作只是 continue),否则复杂度就会错(因为无用点连出去的边又贡献上时间复杂度了)。所以我们也要对一个点上的边按照度数进行从大到小排序,方便 break

接下来我们考虑整改 dp 过程。显然我们要先把有用点的 dp 值全部递归下去算出来,然后考虑无用点怎么办,因为我这个点 dp 的过程中选最小的一些是既可以选无用点也可以选有用点的。

我们考虑无用点在删除的时候,就提前把贡献挂到它连接的那些点上。可以发现,无用点对周围所有的边全都是想选就选的状态,所以我们把周围每条边的边权直接挂到对应的另一个点上去,并且之后都保持这种选择的存在就行了。显然可以给每个点开一个不清空的堆来方便地实现我们的目的。

然后每次 dp 时,从堆中一个一个取出需要的直到达到替换数量的下界就够了,注意还需要复原堆的原状态(也就是只有无用点的状态),而且为了保证复杂度只能把弹出的东西塞回去,加进去的东西倒腾出来。由于无用点可能把树划分成了很多块,所以要扫一遍目前的有用点集合,如果遇到还没做 dp 的,做一次,加进答案即可。

这里也还是有替换代价为负数要全部取完的细节问题,需要注意。

随着 \(k\) 增大做且总是在扫有用点的复杂度是对的。因为每个点最多只会扫到度数次,度数之和是线性,带上堆的总复杂度即为 \(\mathcal O(n\log n)\)

常数有点大,所以只能用可删堆,而且堆太大的话,没用的东西要及时弹出来,真的恶心。

CF675E Trains and Statistic

一做题单就什么都不会。

考虑 \(i\to j\) 的策略如何。考虑其本质是选择一堆区间覆盖掉 \(i\to j\),且第一个区间已经固定好了(T_T,忘记了这回事写出来了一个假算),那么我下一个区间的右端点肯定越靠右越好,所以就直接在每次可以到达的范围内选择 \(a_i\) 最大的点跳过去就行了。

下面考虑怎么求和。设 \(f_i=\sum\limits_{j=i}^n \rho_{i,j}\),那么转移的时候,我们找出 \(i\) 后面可达范围内最大的那个位置 \(j\),这容易数据结构取得。然后考虑 \(f_i\gets f_j\)

可以发现,\(f_j\) 里面绝大多数位置从 \(i\) 出发时都只能 \(i\to j\to \cdots\),这些位置给 \(f_i\) 的贡献恰好比给 \(f_j\) 的多 \(1\)。由于这一步必须有效,所以 \(a_j>a_i\),又因为 \(i<j\),那么 \((j,a_i]\) 的这些位置不比原来多贡献。最后,还要算上 \((i,j]\) 这部分。

可以先直接给 \(f_j\) 里面全部的位置都贡献加 \(1\) 转移过来,再斥掉 \((j,a_i]\) 的部分,最后加上 \((i,j]\)。计算比较方便。写个数据结构即可。

posted @ 2024-04-05 11:34  Shunpower  阅读(33)  评论(0)    收藏  举报