AtCoder Beginner Contest 题目选解
ABC262F Erase and Rotate Future 7.5
先考虑没有旋转的时候怎么做:此时考虑哪些数能成为最终排列的第一个数。显然就是 \(p_1,p_2,\cdots,p_{K+1}\) 中的一个。那么我们选出来这些数中的最小值然后把它挪到最前头就行了。
不断模拟这个过程,用某些数据结构优化一下求最小值的过程就 \(O(N\log N)\) 了。不难用单调队列做到 \(O(N)\)。注意需要特判一下,如果当前能直接删完就全都删完,不需要保留。
现在考虑有旋转的时候怎么做。
一个常见思路是枚举转了多少次然后再考虑没有旋转的情形,但是先转后删和先删后转貌似不太一样......如果先删掉最后一个数再转,我们先转的话就需要转两次再删一次。
我们思考一下发现如果我们先把最后一个数转到了最前头然后把他干掉了那么显然是亏大了
所以我们可以认为转过去之后这个数就 free 了,可以免费删掉,这样看上去就很对了。
接下来还需要枚举转多少次,看上去复杂度还要乘一个 \(K\)。
但实际上并不需要。我们来证明:设 \(x\) 为 \([N-K+1,N]\) 中的最小值位置,那么,最优解要么是直接把 \(x\) 转到最前面,要么是干脆不转。
证明其实非常简单:
- 如果我们把 \(x\) 前面的某个数转到了最前面,那么由于第一步我们总会删掉最小值前面的所有数,那么 \(x\) 前面的这些数相当于白转了(不过删掉这些数的代价为 \(0\) 所以其实是等价的)
- 如果我们把 \(x\) 后面的某个数转到了最前面,那么我们考虑此时 \(p_1\) 的候选集合,这个集合可以分为 \(p[x\cdots N]\) 与 \(p[1\cdots K-(N-x+1)]\) 两部分。对于第一部分,这些数都小于 \(x\),显然不优;对于第二部分,显然不如我们直接干脆不转的候选集合要优(因为不转的话候选集合是 \([1,K]\),直接覆盖了这个集合)
因此只需要做两次不带旋转的情况就好了。复杂度可以是 \(O(N\log N)\) 或 \(O(N)\)。
ABC262G LIS with Stack Future 8.2
相当于选出一个最长的子序列,使得这个子序列能够通过一个栈排序。
通过思考发现,一个排列 \(a\) 能通过一个栈排序,当且仅当不存在 \(i<j<k\) 使得:\(a_j>a_i>a_k\)。
证明:
若存在 \(i<j<k\) 使得 \(a_j>a_i>a_k\),那么 \(a_i\) 最先入栈,需要在 \(a_j\) 之前出栈,但又要在 \(a_k\) 之后出栈,故此时不可能将其排序。
反之,使用归纳法:若 \(\forall k<n\),对长为 \(k\) 的排列均有结论成立,那么当 \(k=n\) 时,记 \(a_j\) 为最小的 \(j\) 满足 \(a_j>a_1\),那么 \(\forall x\ge j\) 有 \(a_x>a_1\),\(\forall 1<x<j\) 有 \(a_x<a_1\)。这表明我们可以将 \(a_1\) 先入栈,然后将 \(a[2\cdots j-1]\) 扔进去排序再出栈,将 \(a_1\) 出栈,然后将 \(a[j+1\cdots n]\) 扔进去排序再出栈。
那么我们 DP 的过程其实就是把这个证明的构造过程写进去。
我们考虑选不选 \(a_1\),如果选了 \(a_1\),我们枚举这个分界点 \(j\),现在要在 \([2,j-1]\) 中选出 \([1,a_1-1]\) 中的数,在 \([j+1,n]\) 中选出 \([a_1+1,n]\) 中的数。
那么可以设 \(\text{dp}[s][t][l][r]\) 表示 \(a[s\cdots t]\) 中选出 \([l,r]\) 内的数,最多能选多少。转移需要枚举分界点,因此总的时间复杂度为 \(O(n^5)\)。
可以发现后两维远远卡不满 \(O(n^2)\),因此完全能过。AC Code
ABC262Ex Max Limited Sequence Future 8.3
区间 \([L_i,R_i]\) 的最大值为 \(X_i\) 相当于 \(\forall L_i\le x\le R_i,A_x\le X_i\) 以及 \(\exist L_i\le x\le R_i,A_x=X_i\)。
我们维护一个 \(B_i\) 表示 \(A_i\) 可能的最大值,对每个 \((L_i,R_i,X_i)\),我们对 \([L_i,R_i]\) 区间中的每个 \(j\) 执行 \(B_j\leftarrow\min(B_j,X_i)\)。
接下来把所有 \(B_i\) 相同的 \(i\) 拎出来,那么对每个 \(X_j=B_i\) 的限制,区间中 \(=X_j\) 的数只能由这些位置来贡献。
把 \(=X_j\) 的数看成 \(1\),\(<X_j\) 的数看成 \(0\),此时问题转化为:一个序列,有若干限制,每条形如 \([l,r]\) 中至少有一个 \(1\),\(0\) 的方案有 \(X_j\) 种,求符合条件的序列数量。
这个就是 CF1327F,可以做到线性。也可以用 DP-W 的方法做到线性。
复杂度 \(O((N+M)\log N)\)。AC Code
ABC265G 012 Inversion Present 7.5
考虑线段树,维护区间 \(0/1/2/10/20/21/01/02/12\) 的个数以及置换 \(f:\{0,1,2\}\to \{0,1,2\}\),每次打标记相当于要乘一个置换,区间逆序对的个数就是 \(10,20,21\) 的个数之和。
于是这题就做完了,\(O(N+Q\log N)\)。
ABC263G Erasing Prime Pairs Present 7.5
当时写的题解:
对于 \(i\neq j\) 若 \(A_i+A_j\) 为素数我们连边 \((i,j)\),那么这张图必然是一张二分图。
证明:若图中出现奇环 \(u_1,u_2,\cdots,u_k,u_{k+1}=u_1\),考虑 \(S=\sum_{i=1}^kA_{u_i}+A_{u_{i+1}}\)。一方面 \(S=2\sum A_{u_i}\),因此 \(S\) 必然是偶数;另一方面 \(S\) 是奇数个素数之和,而由 \(A_i\) 互不相同可知加起来只能是奇素数,因此 \(S\) 同时也是个奇数,矛盾!因此图中不存在奇环,即这张图是二分图。
那么我们给二分图染个色跑带权最大匹配就行了。
然而 \(1\) 自己和自己也能消,我的做法是:在跑完最大匹配之后的残量网络上考虑 \(1\) 对应边的剩余流量 \(w\),把答案加上 \(\lfloor w/2\rfloor\) 即可。但是 WA 了。
其实这个做法是错的。有可能我们先不让 \(1\) 匹配,此时通过调整流量做到更优。
正确的做法是:我们把每个点拆成两个,左边一个右边一个,然后正常从左往右连边。此时 \(1\to 1\) 自己和自己匹配的情况已经被我们算进了网络流的情况中,于是就对了。这样一来答案还要除以 \(2\),因为 \(u_1\to v_2,u_2\to v_1\) 被算了两遍。
复杂度是 \(O(\text{maxflow}(N,N^2))\)。AC Code
ABC263Ex Intersection 2 Present 7.5
首先二分答案,转化为与 \((0,0)\) 距离 \(\le r\) 的交点有多少个。
然后我们算出来每条直线和圆的两个交点,按极角排序然后随便拉个 ds 过来做做就行了。
代码:AC Code
ABC261G Replace Future 8.5
我们倒着考虑变换,看能否把 \(T\) 缩成 \(S\)。那么其实只要算出来 \(f(l,r,c)\) 表示 \(T[l\cdots r]\) 缩成字符 \(c\) 所需的最小步数就行了。
怎么算 \(f(l,r,c)\) 呢?我们考虑枚举变成 \(c\) 上一步的字符串是啥样的。如果最后一步是 $A_i\to C_i $,那么 \(A_i\) 中每个字符在 \(T[l\cdots r]\) 中最后对应字符串的相对位置是不变的。
因此相当于要将 \(T[l\cdots r]\) 分成 \(|A_i|\) 个子段,然后每个字段对应一个字符。我们发现,这刚好变成了一个规模更小的子问题。
具体来说,我们把 \(T[l\cdots r]\) 这个字符串拎出来当成一个单独的字符串 \(L\),然后在 \(L\) 和 \(A_i\) 上做 dp:\(g(i,j)\) 表示 \(L[1\cdots i]\) 这个子段,用 \(A_i[1\cdots j]\) 来达到的最小代价。转移就是
然后我们发现 \(g\) 的状态其实大部分都是重复的,因此可以这么设状态:
- \(f(i,j,c)\) 表示 \(S[i\cdots j]\) 缩成一个字符 \(c\) 的最小步数;
- \(g(i,j,k,l)\) 表示 \(S[i\cdots j]\) 缩成 \(A_k[1\cdots l]\) 的最小步数,注意,这实际上是把之前的状态 \(g(i,j)\) 写成了完整的 DP。但是这样一来,我们就少了很多重复的转移。
转移就和之前差不多了。于是就能过了。AC Code
ABC261Ex Game on Graph Present 7.5
我们先考虑这样一个题目:
有一个 \(n\) 点 \(m\) 边的有向图,一开始有个硬币在节点 \(1\) 上。两个人轮流操作,每次可以从某个点 \(u\) 沿一条出边 \(u\to v\) 走到 \(v\),不能走的人输。
给定初始节点,输出先手后手谁赢或者平局。\(1\le n,m\le 2\times 10^5\)。
如果图是 DAG 当然非常好做。当图中有环的时候怎么办呢
你可能会说如果在环里面开始那么必然平局,但是事实并非如此。比如
下面我们给出正确的算法:
- 首先,对于没有出边的所有点 \(u\),我们将 \(u\) 标记为「先手必败」,并将 \(u\) 压入队列 \(\bold{Q}\)。
- 接下来我们不断从 \(\bold Q\) 中取出队首 \(u\),并且枚举所有能够从 \(v\to u\) 的 \(v\),分两种情况讨论:
- 如果 \(v\) 已经被标记过了,我们可以直接不管他。否则:
- 如果 \(u\) 是「先手必败」,那么将 \(v\) 标记为「先手必胜」。
- 否则, 将 \(v\) 的出度减一。此时,如果 \(v\) 的出度已经为 \(0\),我们将 \(v\) 标记为「先手必败」。
最终,没有被标记的节点就是平局的节点,剩下的都已经被标记过答案了。建议手搓样例理解一下。
对于这个题,我们可以做一个类似的过程:设 \(f_u,g_u\) 分别表示从 \(u\) 开始,先手/后手的答案。
- 将所有没有出边的所有点 \(u\) 的答案 \(f_u,g_u\) 标记为 \(0\),并将所有这样的 \(f_u,g_u\) 压到堆里面。其余节点均为 \(+\infty\)。
- 每次我们取出堆中最小的一个数,然后分类讨论:
- 若其为某个 \(f_u\),枚举所有能从 \(v\to u\) 的节点 \(v\),直接用 \(f_u+w\) 更新 \(g_v\),如果更新成功就将 \(g_v\) 插入堆。
- 否则如果是某个 \(g_u\),我们枚举 \(v\),用 \(g_u+w\) 更新 \(f_v\);如果 \(v\) 已经被它的所有出节点更新,就将 \(f_v\) 插入堆。
这样做的正确性在于,图中的边权均为正数,因此类似 dijkstra 算法,\(f\) 总是要取最小值,因此第一次更新时就是正确的值。
如果有负边权呢?类似 SPFA 算法,我们只要不断重复这个过程直到这个数收敛即可。
有没有感觉和最短路算法很像?
时间复杂度 \(O((N+M)\log M)\)。AC Code