做题记录
- Two Missing Numbers
- Sets May Be Good
- Text Editor
- COW Operations
- Not Intersect
- Bracket Xoring
- 前缀和
- Fugitive Frenzy
- Palindrome Addicts
- Hierarchies of Judges
- Computational Intelligence
- Group Division
- Counting Prefixes
- Can Bash Save the Day?
- Wine Factory
- Most Different Tree
- Finding Satisfactory Solutions
- Smooth Sailing
- Minimum Cost Flow²
- String Journey
Two Missing Numbers
Source
Statement
通信题。有一个长度为 \(n\) 的序列 \(a\ (0\le a_i<2^{64})\),满足其中恰好两种数出现次数为 \(1\),其余数字出现次数为 \(2\)。该序列被任意分成两半,分两次喂给你的程序。第一次运行你会得到序列的前半段,你需要输出两个 \(\mathtt{uint64}\);第二次运行你会得到序列的后半段以及第一次输出的两个整数,你需要输出两个 \(\mathtt{uint64}\) 表示那两个出现次数为 \(1\) 的数。
Solution
首先把已经出现 \(2\) 次的数字扔了。现在相当于分两次获得集合 \(S,T\),需要找到 \(S\oplus T\),记这个结果为 \(\{x,y\}\)。
考虑神秘哈希,记 \(P=18446744073709551557\),即 \([0,2^{64})\) 中的最大质数。考虑建立一个简单的单射 \(f:[0,2^{64})\cap a\to[0,P)\cap\mathbb{Z}\)。一个简单的想法是 \(f(x)=x\operatorname{xor} M\),其中 \(M\) 是一个自己脸滚键盘得到的常数。因为 \(2^{64}-P=59\),所以可以认为 \(f(a_i)\ge P\) 的概率很小(如果非常不幸出现了,可以再滚一个 \(M\) 提交)。
第一次运行,我们计算 \(\displaystyle a_1=\sum_{x\in S}f(x)\bmod P,\ b_1=\prod_{x\in S}f(x)\bmod P\),第二次运行类似地计算 \(a_2,b_2\)。接下来,依次尝试以下几种情形:
-
\(x\in T\land y\in T\):
可以得到:
\[\begin{cases} f(x)+f(y)\equiv a_2-a_1\pmod P \\ f(x)\cdot f(y)\equiv b_2/b_1\pmod P \end{cases} \]直接韦达定理解二次方程(需要 Cipolla 开根)。然后映射回去,验证答案。
-
\(x\in T\land y\in S\):
可以得到:
\[\begin{cases} f(x)-f(y)\equiv a_2-a_1\pmod P \\ f(x)/f(y)\equiv b_2/b_1\pmod P \end{cases} \]是一个一次方程,直接解。然后映射回去,验证答案。
-
\(x\in S\land y\in S\):
可以得到:
\[\begin{cases} f(x)+f(y)\equiv a_1-a_2\pmod P \\ f(x)\cdot f(y)\equiv b_1/b_2\pmod P \end{cases} \]也是二次方程,直接解。然后映射回去,验证不了,不验证了。
由题目限制可知至少解出一个解。为什么要按照上面的顺序依次尝试呢?因为解这个方程时只知道 \(T\),所以按照验证难度从易到难尝试错误率更小。
Code
Sets May Be Good
Source
Statement
给定 \(n\ (n\le 1000)\) 个点无向图 \(G=(V,E)\)。求导出子图边数为偶数的点集数量。
Solution
考虑一个 \(n\) 元二次多项式 \(\displaystyle F(x_1,\dots,x_n):=\sum_{i\neq j,\ (i,j)\in E}x_ix_j+\sum_{(i,i)\in E}x_i\)。题目相当于求 \(n\) 元组 \((x_1,\dots,x_n)\in \{0,1\}^n\) 使得 \(F(x_1,\dots,x_n)\bmod 2=0\)。
直接计算 \(=0\) 的方案有些困难,所以可以考虑计算 \(=0\) 和等于 \(=1\) 的方案数差值。
考虑拆开 \(F\):\(F(x_1,\dots,x_2)=x_1L(x_2,\dots,x_n)+Q(x_2,\dots,x_n)\)。
如果 \(L(x_2,\dots,x_n)=1\),那么对于每一种 \(x_2,\dots,x_n\) 方案,\(x_1\) 选与不选恰好对应两种最终 \(F\) 值不同的方案,所以对差值的贡献为 \(0\)。
那么只剩下 \(L(x_2,\dots,x_n)=0\) 的情形。注意到 \(L(x_2,\dots,x_n)\) 是一个一次的多项式,所以可以移项得到 \(x_2\) 关于 \(x_3,\dots,x_n\) 的表达式。将这个表达式回带到 \(Q\) 得到 \(Q'(x_3,\dots,x_n)\)。那么我们就是要求 \(Q'(x_3,\dots,x_n)=k\ (k\in\{0,1\})\) 的方案数,这是一个形式相同子问题,递归计算即可。
时间复杂度 \(O(n^3)\)(吐槽数据范围)。
HoMaMaOvO 0:06 过了,天知道怎么做到的。
Text Editor
Source
Statement
Run-twice problem。要求维护数据结构维护可见字符组成的字符串,支持以下操作:
- \(\texttt{insert}(p,s)\):在位置 \(p\) 插入一个字符串 \(s\)。
- \(\texttt{erase}(l,r)\):移除 \([l,r)\) 子串。
- \(\texttt{print}(l,r)\):打印 \([l,r)\) 子串。
- \(\texttt{copy}(l,r)\):将 \([l,r)\) 拷贝至剪贴板。
- \(\texttt{cut}(l,r)\):将 \([l,r)\) 剪切至剪贴板。
- \(\texttt{paste}(p)\):在位置 \(p\) 后拷贝剪贴板内容。
- \(\texttt{undo}\):撤销(不改变剪贴板)。
- \(\texttt{redo}\):重做(不改变剪贴板)。
第一次运行的最后一个命令是 \(\texttt{serialize}\),你需要输出一个可见字符组成的字符串 \(s\)。
然后,第二次运行的第一个命令是 \(\texttt{deserialize}\),你可以知道上次运行输出的 \(s\),然后需要还原上次运行结束时文本编辑器的状态。
操作次数 \(n\le 10^4\),你输出的字符串要求 \(|s|\le 10^7\)。
Solution
首先这个东西只能用可持久化 Treap 维护。
于是这题就是让你维护一个可持久化 Treap,并且支持 dump/load。
朴素维护的话用的空间太多了。但是我们发现,一个平衡树的节点可以表示不止一个字符。具体而言,把所有 \(\texttt{insert}\) 操作的字符串拼成一串得到 \(S\),这样每个平衡树节点就可以只维护一对 \(l,r\) 表示该节点的字符串是 \(S[l:r]\)。进行 Treap 分裂时如果恰好裂在某个节点中间,就把这个节点拆成两个。
这样节点数量和需要维护的信息数量都少了很多。\(\texttt{serialize}\) 只需要直接把节点信息压缩打印就行了。应该可以过(吧)。
COW Operations
Source
Statement
给定长度 \(n\) 的字符串 \(s\),字符集为 \(\{\mathtt{C},\mathtt{O},\mathtt{W}\}\)。你可以进行下面的操作之一若干次:
- 选择两个相同的相邻字符并删除;
- 选择一个字符,将其替换为另外两种字符各一,顺序任意,如 \(\mathtt{COW}\) 可以操作为 \(\mathtt{CWCW}\)。
\(q\) 次询问 \(l,r\),求子串 \(s[l:r]\) 是否能够通过上述操作变成 \(\mathtt{C}\)。
\(1\le n,q\le 2\times 10^5\)。
Solution
将 \(\mathtt{C},\mathtt{O},\mathtt{W}\) 视作 \(1,2,3\)。考虑一个神奇的性质:\(1\operatorname{xor}2=3,\ 2\operatorname{xor}3=1,\ 3\operatorname{xor}1=2\)。所以,两种操作都不会改变整个字符串的异或和。
我们又发现,如果存在相同的相邻字符,可以直接使用操作 \(1\),字符串长度减少 \(2\);否则,先使用操作 \(2\),然后必然可以使用操作 \(1\),字符串长度减少 \(1\)。所以我们必定能够删到只剩一个字符。
因此问题等价于判断 \([l,r]\) 异或和是否为 \(1\)。
Not Intersect
Source
Statement
有一个圆,上面逆时针均匀放置着 \(n\) 个点,标号 \(1\sim n\)。你需要画 \(m\) 条线段,要求每个线段必须连接 \(n\) 个点中两个不同的点,且线段之间两两不交。求方案数 \(\bmod 10^9+7\)。
\(1\le n\le 10^7,\ 0\le m\le \dfrac{n(n-1)}{2}\)。
Solution
我们改为解决这个的问题:有一个 \(n\) 段的圆弧,\((1,n)\) 之间已经连了一条线段,要再连 \(m\) 条不相交的线段,并且边与圆弧不重(也就是不能有 \((i,i+1)\) 的线段)。设这样的答案为 \(f_{n,m}\)。
那么原题答案显然是 \(\displaystyle\sum_{i=0}^m f_{n-1,i}\binom{n}{m-i}\),也就是再加入一些与圆弧重的边。
我们考虑求 \(f\) 的生成函数 \(\displaystyle F(x,y)=\sum_{n,m}f_{n,m}x^ny^m\)。
考虑边界上最靠外的若干条边,这些边可以看做是将圆弧分成若干段,而每个段内都是形式相同的子问题,如图:
我们先假装把 \((1,n)\) 的边也计入 \(m\),那么有方程:
其中,那个分式相当于至少 \(2\) 个 \(F\) 相乘(因为我们不能只分一段),那个 \(x\) 是边界 \(f_{1,0}=1\)。
这样解出来是一个二次方程,因此会有两个解,我们需要选择那个 \([x^0]F(x,y)=0\) 的解。
最后,我们要把假装加入的 \((1,n)\) 去除,即,除了 \(x^1\) 之外所有 \(x^i\) 系数都要除以一个 \(y\):
经过艰苦卓绝的使用 Mathematica 计算,可以得到最终的 \(F\):
然后我们就要计算答案啦。
我们发现,分母上的东西都被搞掉了,于是我们只需要展开分子。这里分子是两部分的乘积,我们尝试展开前半部分,这里显然只需要管那个根式:
这里的两个组合数虽然看着十分鬼畜,但是都可以用 \(\dbinom{n}{m}=\dfrac{n^{\underline{m}}}{m!}\) 来拆成下降幂算。至于负数的下降幂,可以每一项提出 \(-1\) 之后转成正数的上升幂。注意到这里隐含了要求 \(m\le n\),所以 \(O(n)\) 预处理后整个式子都可以 \(O(1)\) 记算。
回到上面的式子,既然左边可以 \(O(1)\),右边是一个简单的组合数,那么就可以 \(O(n)\) 计算出 \([x^{n-1}y^{m+1}]\) 的系数,那么整道题就做完啦 ヾ(≧∇≦*)ヾ。
Bracket Xoring
Source
- CodeTON Round 7 - Problem F (CF1896F)
Statement
给定长度为 \(2n\) 的 \(\tt 01\) 字符串 \(s\),你可以进行下面的操作:
- 选定长度为 \(2n\) 的合法括号串 \(t\),对于每一对匹配的括号 \(t_i,t_j\),将 \(s[i:j]\) 进行 \(\tt 01\) 反转。
使用不超过 \(10\) 次操作将 \(s\) 变为全 \(0\)。
\(1\le n\le 2\times 10^5\)。
Solution
首先进行一些简单的观察:如果 \(s_1\neq s_{2n}\) 或者 \(\sum_i [s_i=1]\bmod 2\neq 0\),那么无解。
考虑选定一个 \(t\) 之后对 \(s\) 的影响是什么。题目的操作相当于将括号树上奇数层的括号对位置异或 \(1\),而括号树上深度的奇偶性实际上等于左括号下标的奇偶性。于是我们可以把操作等价为:
- 对于所有 \(1\le i\le 2n\),令 \(s_i\gets s_i\oplus (i\bmod 2)\oplus [t_i=\mathtt{)}]\)。
于是我们就可以判断是否能用 \(1\) 次操作完成了。
接下来考虑能否使用 \(2\) 次操作完成,此时显然需要满足 \(s_1=s_{2n}=0\),那么我们的操作变成:
- 对于所有 \(1\le i\le 2n\),令 \(s_i\gets s_i\oplus [t_{1,i}=\mathtt{)}]\oplus[t_{2,i}=\mathtt{)}]\)。
那么对于 \(s_i=0\),相当于限制两个括号串在这个位置相同,而 \(s_i=1\) 则是要求不同。
那么我们可以这样构造:\(t_{1,1}=t_{2,1}=\mathtt{(},t_{1,2n}=t_{2,2n}=\mathtt{)}\),然后对于 \([2,2n-1]\) 中的 \(s_i=0\) 位置,\(t_1,t_2\) 交替填 \(\mathtt{()}\),对于 \(s_i=1\) 的位置,\(t_1\) 交替填 \(\mathtt{()}\),\(t_2\) 交替填 \(\mathtt{)(}\)。可以发现这样子填括号串一定是合法的。
如果 \(s_1=s_{2n}=1\) 的话,我们就随便进行一个操作,使得 \(s_1=s_{2n}=0\),转化成上面的情况。于是我们使用了不超过 \(3\) 次操作完成了这题。
前缀和
Source
- THUPC 2024 初赛 - Problem C
Statement
独立地以下面的分布生成 \(n\) 个随机数 \(x_1,\dots,x_n\):
记 \(x_1,\dots,x_n\) 前缀和数组为 \(s_1,\dots,s_n\)。给定 \(n,l,r,p\),求 \(s_i\in[l,r]\) 的 \(i\) 个数期望值。
Solution
假设存在无限长的灯带,每盏灯有 \(p\) 概率是亮的。那么 \(x_i\) 就是第 \(1\) 盏灯的位置,\(s_i\) 就是第 \(i\) 盏灯的位置。所以答案就是 \([l,r]\) 间期望灯的数量,为 \((r-l+1)p\)。
Fugitive Frenzy
Source
- ICPC 2023–2024, NERC - Problem F (CF1912F)
Statement
在一个 \(n\) 个点的树上进行警察抓小偷。警察先出现在给定的节点 \(s\),然后小偷选择一个节点 \(b\) 出现。接着警察和小偷交替操作,警察先手:警察每一步可以花费 \(1\) 单位时间移动到 \(s\) 的一个相邻节点 \(s'\),小偷每一步可以瞬间移动到节点 \(b'\) 满足路径 \(b\leadsto b'\) 不经过 \(s\),二者都可以选择不动。警察和小偷位于同一个节点时警察抓到小偷。警察不知道小偷的位置,因此他们都会选择概率性的策略,警察希望最小化期望抓捕时间,而小偷希望最大化。
求在双方使用最优策略时期望抓捕时间,答案相对或绝对误差不能超过 \(10^{-6}\)。
\(2\le n\le 100\)。
Solution
结论:假设警察当前位于 \(u\),那么她一定会以一个概率分布 \(p_u\) 选择一个叶子节点走下去。而小偷也会以一个概率分布 \(q_u\) 瞬移到一个叶子,并且一直等待到警察走到另一个叶子或把自己抓住。
下面是简单证明:
-
对于警察:
对于初始情形(位于 \(s\)),小偷可以位于任何地方,警察不知道任何信息。那么警察会一步步走到某个叶子,因为最终的结果都是走到某个叶子,不同的走法本质相同,所以她一定走最短路。而到了叶子后,如果游戏还未结束,那么小偷此时可以瞬移到任何地方,因为所有路径都不经过叶子。于是警察关于小偷的位置不知道任何信息,局面和初始情形一致,所以她仍然会采取相同的策略。
-
对于小偷:
小偷希望最大化抓捕时间,因此一定会拖延到叶子再被抓住(如果会被抓住的话)。而随着警察向下走,小偷可以活动的范围有可能被缩小,可行的决策减少,因此小偷一定会在最开始的时候就选定一个叶子蹲着。
于是我们设 \(f_u\) 表示警察位于 \(u\) 时期望还需要多久游戏结束,\(p_{u,v}\) 表示警察处于 \(u\) 时选定 \(v\) 的概率,\(q_{u,v}\) 表示警察处于 \(u\) 时小偷选定 \(v\) 的概率,\(L\) 表示所有叶子的集合,那么:
因为在最优策略下,双方都无法调整 \(p,q\) 使得答案变优,所以我们有:
而我们知道 \(\dfrac{\partial q_{u,v}}{\partial f_u}=p_{u,v}f_v\),所以 \(p_{u,v}\sim \dfrac{1}{f_v}\),那么:
这个解满足 \(0\le p_{u,v}\le 1,\ \sum_vp_{u,v}=1\) 的约束条件,因此一定会取到。
将这个式子回带到 \(f_u\) 的表达式:
这个东西和 \(p,q\) 无关,我们可以不断应用上面的式子迭代,求出最后的解。这里迭代次数官方题解没证,我更不会证,那就不证了。复杂度 \(O(n^2+m|L|^2)\),其中 \(m\) 为迭代次数。
Code
Palindrome Addicts
Source
- Codeforces Global Round 22 - Problem H (CF1738H)
Statement
维护字符串 \(s\),\(q\) 次操作,每次 push back 或者 pop front,然后求 \(s\) 的本质不同回文串个数。
\(1\le q\le 10^6,\ |\Sigma|=26\)。
Solution
添加或删除一个字符的时候,本质不同回文串数量只会变化最多 \(1\),所以只需要判断最长回文前/后缀是否只出现一次。
离线,求出所有 push back 操作组成的字符串 \(s\),然后对这个字符串的正反串建回文自动机,分别记作 \(T_1,T_2\)。然后每次相当于查询一个子串 \(s[l:r]\)。
- push back 时:在 \(T_1\) 的 fail 树上倍增跳找到最长回文后缀,然后查询这个后缀最后出现的位置,如果这个位置 \(<l+len-1\) 那么其是第一次出现。每次加入字符时,相当于给 fail 树上一条到根的链上所有节点的最后出现位置 check max,可以用单点修改区间查询的线段树维护。
- pop front 时:在 \(T_2\) 的 fail 树上倍增跳找到最长回文前缀,接着找到这个回文前缀在 \(T_1\) 上对应的节点,然后查询其最后一次出现位置。要找到 \(T_2\) 节点对应的 \(T_1\) 节点,可以先求出该节点对应的串的最靠前出现位置,然后进一步找到其对应的 \(T_1\) 节点。
时间复杂度 \(O(q\log n)\)。
Code
Hierarchies of Judges
Source
Statement
计数满足以下条件的 \(n\) 个节点的有标号有根树的数量:
- 每个节点有颜色黑色或白色。
- 对于每个节点,其本身以及其儿子中,至少一半的节点是白色的。
两棵树被认为不同当且仅当:
- 对于某个节点,其在两棵树中颜色不同。
- 对于某个节点,其儿子的集合不同。
- 对于某个节点,其白色儿子的相对顺序不同。
答案对 \(998244353\) 取模。\(1\le n\le 2\times 10^5\)。
Solution
因为把巨大牛迭的 \(O(n \log n)\) 分析成了 \(O(n \log^2 n)\) 导致 ucup 过题 \(-1\),警钟金属键断裂。
考虑 EGF。设根为黑色的 EGF 为 \(F_0(x)\),根为白色的 EGF 为 \(F_1(x)\),则答案是 \(\left[\dfrac{x^n}{n!}\right](F_0(x)+F_1(x))\)。
那么列出关于 \(F_0(x),F_1(x)\) 的方程,枚举黑色/白色的儿子数量:
把后面的 \(F_0(x)^i\) 求和用等比数列求和重写,然后整个式子又可以用 \(\exp\) 重写:
那么我们把方程组整理一下,直接用 \(F_0,F_1\) 表示 \(F_0(x),F_1(x)\):
考虑使用多元函数的牛顿迭代法。原方程组的 Hessian 矩阵为:
设 \(F_0(x),F_1(x)\) 为 \(\bmod x^{n}\) 的结果,\(F_0^*,F_1^*\) 为 \(\bmod x^{2n}\) 的结果,那么:
经计算可以验证,\(H\) 总是存在逆,故迭代可以进行。该方法正确性的证明和传统的多项式牛顿迭代是类似的。
在计算过程中,需要进行多项式乘法、多项式求逆、多项式 \(\exp\),因此复杂度 \(T(n)=T\left(\dfrac{n}{2}\right)+O(n\log n)=O(n\log n)\)。常数很大,但是足以通过。
Code
Computational Intelligence
Source
Statement
给定平面上两个线段 \(l_1,l_2\),有两点 \(P\in l_1,\ Q\in l_2\) 均匀随机,求 \(|P-Q|\) 的期望。绝对或相对误差不超过 \(10^{-9}\),\(10^5\) 组数据。
Solution
将线段表示成点向量形式,于是 \(\overrightarrow{OP}=\boldsymbol{u_1}+x \boldsymbol{v_1},\ \overrightarrow{OQ}=\boldsymbol{u_2}+y\boldsymbol{v_2}\)。那么所求的期望可以写成:
开积!但是使用一般的方法,积到下辈子都积不出来,所以我们需要使用多重积分的换元法。
令 \(u\gets A_1 x+B_1 y+C_1,\ v\gets A_2x+B_2 y+C_2\),则原积分转化为:
其中 \(D\) 是变换后的二维区域。
由于 \((u,v)\) 是 \((x,y)\) 的仿射变换,所以其逆变换也是仿射变化,故 Jacobian 行列式是常量,可以先忽略。而原始的积分区域是 \([0,1]\times[0,1]\),所以 \(D\) 显然是一个平行四边形,那么原积分可以拆成若干个下面形式的积分:
这个东西事实上可以使用 Mathematica 计算出原函数(相当复杂),于是可以 \(O(1)\) 计算。但使用数值积分应当也可以。
值得注意的是,当 \(A_1B_2-A_2B_1=0\) 时,原积分式中的 Jacobian 行列式不存在(因为此时 \(D=\varnothing\))。所以此时需要使用特殊的还原方法,过程类似,不赘述。
朴素的实现可能会被卡精度。在拆分平行四边形的时候,如果一条线段的 \(\Delta u\) 较小,可能会导致可怕的误差。由于 \(u,v\) 地位对称,可以交换,因此可以选择极差较大的一维作为 \(u\)。
Code
Group Division
Source
- Good Bye 2023 - Problem F (CF1916F)
Statement
给定 \(n_1+n_2\) 个点, \(m\) 条边的无向双连通图,将点集划分成 \(S,T\) 使得 \(|S|=n_1,\ |T|=n_2\) 且 \(S,T\) 内的点连通。构造方案。\(1\le n_1,n_2\le 2000,\ 1\le m\le 5000\)。
Solution
先令 \(S=\{\text{*any magical vertex whatever you want*}\}\),然后每次尝试将一个 \(T\) 中的点移动到 \(S\)。这个点需要满足,不是 \(T\) 中的割点并且与 \(S\) 中某个点直接相连。
我们可以通过反证法说明这样的点是始终存在的:如果不存在,说明 \(S\) 向 \(T\) 的边都连向了割点,那么对于一对 \(u\in T,\ v\in S\),所有 \(u\leadsto v\) 的路径都要经过某个割点,这与图的双连通性矛盾。
时间复杂度 \(O(n(n+m))\)。
Code
Counting Prefixes
Source
- Hello 2024 - Problem E (CF1919E)
Statement
对于一个长为 \(n\) 的只包含 \(1,-1\) 的序列 \(a\),记其前缀和数组为 \(p\)。现在给出 \(n\) 和排序后的 \(p\),计数有多少个序列 \(a\) 满足条件。\(1\le n\le 5000\)。
Solution
把前缀和转化为直线,那么题目相当于告诉我们每一个高度的顶点出现了多少次,记 \(i\) 出现次数为 \(c_i\)。
先枚举 \(s\) 表示最终 \(a_i\) 的和,然后令 \(a\gets\{\underbrace{1,1,\cdots ,1}_{s\text{ 个}},\underbrace{-1,-1,\cdots,-1}_{p_n-s\text{ 个}}\}\)。接着,我们每次向一个位置插入 \(\{-1,1\}\),不断插入就可以构造出最终的序列。最终序列与构造方式是一一对应的。
如果我们在一个高度为 \(k\) 的位置后插入,那么高度为 \(k,k-1\) 的点数量均增加 \(1\)。于是我们可以从高到低考虑。对于最高点,初始只有 \(1\) 个,那么就需要插入 \(c_{p_n}-1\) 次,使用插板法计算方案数;对于高度为 \(p_n-1\),我们知道初始时有几个点,也知道上一次的贡献,于是也能够计算;以此类推,就能够求出方案数了。
一次计算是 \(O(n)\) 的,总复杂度 \(O(n^2)\)。
Can Bash Save the Day?
Source
- Codecraft-17 and Codeforces Round 391 - Problem G (CF757G)
KHIN 推荐的。
Statement
给定 \(n\) 个点的树和一个 \(1\sim n\) 的排列 \(p\),进行 \(q\) 次操作:
- 给定 \(l,r,x\),求 \(\sum_{i=l}^r\operatorname{dis}(p_i,x)\)。
- 给定 \(x\),将 \(p_x,p_{x+1}\) 交换。
强制在线。\(1\le n,q\le 2\times 10^5\)。
Solution
记排列 \(p\) 的逆排列为 \(p^{-1}\),那么查询相当于只有 \(p^{-1}_u\in[l,r]\) 的 \(u\) 才会产生贡献。
首先考虑树分块。那么我们需要查询的就是块内有贡献点的个数及到界点距离之和,这是一个单点修改区间求和的问题,使用 \(O(1)\) 查询 \(O(\sqrt n)\) 修改的分块维护。总时间复杂度 \(O(n\sqrt n)\)。
然后不难发现这个树分块的问题可以继续 reduce,所以可以把树分块改成某种树分治(如点分树),然后每个分治节点用线段树维护单点修改区间求和。时空复杂度均为 \(O(n\log^2 n)\)。
在做法上不太有优化空间了,所以考虑题目性质。注意到修改交换的是相邻元素,也就是说 \(p^{-1}\) 的变化量只有 \(1\),而上面的做法都没有依赖于该性质。在每个点分树节点上维护子树内 \(p^{-1}_u\) 的相对顺序,那么每次修改至多交换一对相邻的数,于是可以直接前缀和维护那个区间求和问题。查询时需要在 \(\log\) 个点分树节点上二分 \(l,r\) 对应的 rank,这里可以用类似分散层叠的小技巧优化成 \(O(\log n)\)。总时间复杂度 \(O(n\log n)\)。
Code
Wine Factory
Source
- Hello 2024 - Problem F2 (CF1919F2)
Statement
给定长度为 \(n\) 的序列 \(a,b\) 和长度为 \(n-1\) 的序列 \(c\)。按照如下方式建造一个魔法酿酒厂:
初始时,所有 factory 中都是空的。按 \(i=1,2,\dots,n\) 的顺序进行下面的操作:
- 向 factory \(i\) 中添加 \(a_i\) 升水;
- wizard \(i\) 将 factory \(i\) 中的至多 \(b_i\) 升水变成酒。即,如果有 \(x\) 升水,那么就会有 \(\min\{x,b_i\}\) 升水变成酒;
- 如果 \(i<n\),将剩下的水(酒不是水)中的至多 \(c_i\) 升输送给 factory \((i+1)\)。即,如果剩下 \(x\) 升水,那么就会有 \(\min\{x,c_i\}\) 升水输送出去。
现在进行 \(q\) 次操作,每次给定 \(p,x,y,z\),进行修改 \(a_p\gets x,b_p\gets y,c_p\gets z\),然后查询进行一次上述操作会产生多少升酒。
\(1\le n,q\le 5\times 10^5,\ 0\le a_i,b_i\le 10^9,\ 0\le c_i\le 10^{18}\)。
Solution
如果写出进入的水量和离开的水量的关系,会有一堆 \(\min,\max\),并不好处理,所以换一种方向思考。
将所有询问离线,维护二元组序列 \((w_i,s_i)\ (1\le i\le q)\) 表示每个询问当前的水量以及总共的酒量,那么我们相当于对于一个区间中的 \(i\),进行如下操作:
- \(w_i\gets w_i+a\);
- \(s_i\gets s_i+\min\{w_i,b\}\);
- \(w_i\gets \max\{0,w_i-b\}\);
- \(w_i\gets \min\{w_i,c\}\)。
这里所有操作都是可以用 segment tree beats 维护的。需要同时维护 check min 和 check max 操作,细节较多。时间复杂度 \(O((n+q)\log q)\)。
Code
Most Different Tree
Source
- Codeforces Round 897 (Div. 2) - Problem F (CF1867F)
Statement
给定 \(n\) 个点的有根树 \(G\),记 \(P(G)=\{\operatorname{subtree}(u)\mid 1\le u\le n\}\)。构造另一个 \(n\) 个点的有根树 \(G'\),使得 \(|\{u\mid \exists\ T\in P(G): \operatorname{subtree}(u)\sim T\}|\) 最小化,其中 \(\sim\) 指有根树的同构。
Solution
我们称存在 \(P(G)\) 中元素与其同构的子树为同构存在的。
注意到一个事实,如果 \(T\) 是同构存在的,那么其所有子树都是同构存在的。那么如果我们能够找到一个大小最小的树 \(T\) 使得其不是同构存在的,那么我们可以令 \(G'\) 为 \(T\) 上方接一条长度为 \(n-|T|\) 的链,这样的 \(G'\) 一定满足题目最小化的条件。
找到最小的 \(T\) 可以枚举 \(k=1,2,\dots,n\),暴力搜索所有大小为 \(k\) 的无标号有根树,并树哈希判断其是否是同构存在的。根据抽屉原理,最多搜索 \(n+1\) 个树就能找到一个不是同构存在的树。精细实现搜索过程即可做到复杂度 \(O(n)\)。
Code
Finding Satisfactory Solutions
Source
- Good Bye 2020 - Problem H (CF1466H)
Statement
有 \(n\) 个顾客与 \(n\) 个物品,每个顾客有一个排列 \(b_i\) 表示他对物品喜好程度的排名。
你有一个物品分配方案的排列 \(a\),表示 \(i\) 号顾客拿到第 \(a_i\) 个物品。
称一个分配方案 \(a\) 是好的,当且仅当不存在一个非空集合 \(S\),使得存在一个分配方案 \(a'\) 满足:
- \(\forall\,i \in S, a'_i \in S\)
- \(\forall\,i \in S\),第 \(i\) 个顾客相对 \(a_i\) 更喜欢 \(a'_i\)(不要求严格更喜欢 \(a'_i\),即 \(a_i\) 可以等于 \(a'_i\))
- \(\exists\,i \in S\),第 \(i\) 个顾相对 \(a_i\) 严格更喜欢 \(a'_i\)。
输入物品分配方案的排列 \(a\),请求出有多少种不同的 \(\{b_1,b_2,\cdots,b_n\}\) 排列组 使得分配方案 \(a\) 是好的。
\(n\le 40\)。
Solution
最终的分配方案是一个排列,构成若干个环。
首先对于每一个 \(i,j\) 满足 \(b_{i,j}=1\),连边 \(i\to j\)(即每个人的标号连向其最喜欢的物品标号),这样会形成一个基环树森林。不难发现这个基环树森林的环一定是最终排列的环之一。于是我们可以将这些环删掉,对剩下的点递归地操作。
考虑我们过程中连出的所有边组成的图会对应多少种方案。对于边 \(i\to j\) 且 \(j\neq a_i\),则 \(j\) 在 \(b_i\) 中的排名比 \(a_i\) 靠前。于是我们可以这样计算:记点 \(i\) 不记 \(i\to a_i\) 的出度是 \(d_i\),则方案数为:
我们进行状压 DP。设 \(f_S\) 表示集合 \(S\) 中的环构成 DAG 的方案数,那么我们可以枚举 DAG 第一层的环构成的集合 \(T\),并且有下面的转移:
其中 \(g(S,T)\) 表示从集合 \(S\) 向 \(T\) 连边的系数之和,为:
注意这里 \(|S|,|T|\) 指的是点数而不是环数。
然后就是这个 \((-1)^{|T|+1}\) 的容斥系数。咋一看这玩意很没道理,但是我们可以证明这是对的。对于实际上第一层有 \(m\) 个环的方案,计算得到的容斥系数之和 \(S_m\) 可以这样计算:
由数学归纳法易得 \(S_m=1\),容斥系数的正确性得证。
于是我们现在有了 \(O(3^n)\) 的做法了,但是还不够。因为我们的系数都只跟大小有关,所以考虑压缩状态:记状态为每种大小的环各出现几次。那么状态数为下面问题的解:
在 \(n=40\) 时状态数也不超过 \(1500\),十分安全。于是就做完了
Code
Smooth Sailing
Source
- Codeforces Round 919 (Div. 2) - Problem F2 (CF1920F2)
Statement
给定 \(n\times m\) 的网格,每个格子可以是 island, ocean 或 underwater volcano。保证 island 格子构成一个连通块。
\(q\) 次询问 \((x,y)\),求出一条 \((x,y)\) 开始的四连通回路,满足其环绕了整个 island,并且最大化路径上任意点到任意 volcano 的最小曼哈顿距离。输出距离。
\(1\le n\cdot m,q\le 3\times 10^5\)。
Solution
首先跑出每个格子 \((x,y)\) 到最近的 volcano 的距离 \(d_{x,y}\)。
考虑如何判定一个回路是否环绕整个 island。从任意一个 island 格子沿着网格边界引出一条射线,那么回路环绕当且仅当回路经过了这条射线奇数次。
那么我们将一个格子 \((x,y)\) 拆成两个点 \((x,y,0),(x,y,1)\),然后和相邻格子进行连边。如果经过了射线就连向对应 \(0/1\) 取反的点,否则连向 \(0/1\) 相同的点。查询就相当于查最大的 \(k\) 满足加入 \(d_{i,j}\ge k\) 的点后 \((x,y,0)\) 与 \((x,y,1)\) 连通。建 Kruskal 重构树求 LCA 即可。
Minimum Cost Flow²
Source
Statement
给定一张边带权图,求其从 \(1\to n\) 的最小平方费用流,即一条边权为 \(c\),流量为 \(f\) 的边费用是 \(cf^2\),总流量为 \(1\),边没有容量上限。
形式化地说,我们要解决下面的最优化问题:
其中 \(\operatorname{out}(u),\operatorname{in}(u)\) 分别表示进入 \(u\) 和离开 \(u\) 的边。
输出最小费用对 \(998244353\) 取模的结果。\(2\le n\le 100\)。
Solution
This is a physics problem.
考虑将 \(f\) 视为电流 \(I\),\(c\) 视为电阻 \(R\),\((1,n)\) 间连接电源,那么这张图形成一个电路。那么总费用就是 \(\sum I^2R\)。学过初中物理的都知道,这个玩意儿就是电路的产热量。根据物理学原理,产热量最小的时候就是电路稳定的时候,即满足基尔霍夫方程组。
设元 \(I_e\) 表示一条边的电流,\(\varphi_u\) 表示一个点的电势,解基尔霍夫方程组即可。
String Journey
Source
Statement
给定长度为 \(n\) 的字符串 \(s\),求最大的 \(k\),满足存在字符串序列 \(t_1,t_2,\dots,t_k\) 和 \(u_1,u_2,\dots,u_{k+1}\),满足 \(t_i\) 是 \(t_{i-1}\) 的真子串且不为空串(\(u_i\) 可以是空串),使得 \(s=u_1t_1u_2t_2\dots u_kt_ku_{k+1}\)。
\(1\le n\le 5\times 10^5\)。
Solution
首先有下面的几个观察:
Observation 1:我们完全可以认为 \(|t_i|=|t_{i-1}|-1\),如果 \(t_{i-1}\) 更长的话可以将其缩短而不影响答案。
Observation 2:对于一个下标 \(i\),如果 \(r\) 满足 \(s[i:r]\) 可以作为某种方案的 \(t_1\),那么更小的 \(r\) 也可以。
有了上面的观察,我们就有了一种思路:设 \(f_i\) 表示最大的 \(x\) 满足 \(s[i:i+x-1]\) 可以作为某种方案的 \(t_1\)(那么 \(k\) 就是 \(r-i+1\))。那么我们考虑二分,问题转变为判定是否有 \(f_i\ge x\)。假设 \(t_2=s[j:j+x-2]\),那么我们写出来 \(j\) 的约束条件:
对于最后一个条件,我们可以使用后缀数组来刻画,先把条件改写成:
于是第三个条件可以刻画成关于 \(\operatorname{rk}(j)\) 的一个区间的限制。我们可以用可持久化线段树维护区间 max 来进行判定。于是有了 \(O(n\log^2 n)\) 的 做法,但是仍然不够高效。
Observation 4:\(i+f_i\le (i+1)+f_{i+1}\),即每个 \(i\) 的最远右端点有单调性,这是直观的。
所以可以进行双指针,将判定次数降到 \(O(n)\),并且不再需要可持久化。复杂度 \(O(n\log n)\)。