随机乱做 Part 9

[2021 ICPC Asia Nanjing Regional Contest(XXII Open Cup, Grand Prix of Nanjing)] Paimon's Tree(树形 DP + 区间 DP)

题面

题意:

有一个有 \(n + 1\) 个点的树,初始每条边没有边权,每个点都是白的。

另外给出一个长度为 \(n\) 的序列 \(a_1 , \dots , a_n\),你初始从树上选择一个点染黑,然后执行下列操作 \(n\) 次:

  • 选中一条连接一个黑点和一个白点的边。
  • 假如这是第 \(i\) 次操作,将这条边的权值赋为 \(a_i\)
  • 将这条边连接的白点染黑。

这样你就得到了一个带边权的树,请最大化它的直径。

数据范围:\(n \le 400\)

对于树上问题,如果没有思路可以先考虑在序列(链)上怎么做。本题中就是考虑选定了树上的一条路径,如何最大化它的长度。

因为问题是扩展区间状物,所以考虑区间 DP,设 \(f_{i,j,k}\) 表示确定了链上的点 \([i,j]\) 之间的边,已经放了 \(a_1\sim a_k\) 的最大路径长度。

容易发现在 \([i,j]\) 之间的点的子树中且不在链上的边对答案没有影响(数量可以直接 dfs 预处理),我们称这些没有贡献的边为“垃圾桶”。

转移有:\(f_{i,j,k}\leftarrow \max(f_{i+1,j,k-1},f_{i,j-1,k-1})+a_k\)\(f_{i,j,k}\leftarrow f_{i,j,k-1}\)\(a_k\) 可以被扔进垃圾桶)。

接下来考虑扩展问题到树上。一个显然的想法是直接把状态设计搬过来,但是我们并不知道两端点垃圾桶的大小(换句话说就是:扩展之后垃圾桶的大小可能和之前状态中转移的大小不一样),考虑多加几维解决这个问题。设 \(f_{i,j,k,0/1,0/1}\) 表示 \(i\to j\) 的路径中已经确定了前 \(k\) 个元素,\(i\)\(j\) 分别是否还能扩展的最大路径长度。也就是说,设路径为 \(i\to i'\to\cdots\to j'\to j\),那么状态就表示当前路径是 \(i/i'\to j/j'\),下一步会向 \(i/j\) 扩展。

可以使用记忆化搜索完成转移。

代码:https://pastebin.ubuntu.com/p/NQ8qJYjnnR/

[Code+#3] 白金元首与莫斯科(状压 DP)

题面

先考虑每次暴力去掉每个点怎么做。

\(f_{i,s}\) 表示确定了前 \(i\) 行,且第 \(i\) 行往下延伸到第 \(i+1\) 行的状态是 \(s\) 的方案数。

每次更新一行,考虑怎么填横着放的格子。为了不重不漏,我们可以从小到大枚举每个位置,决策这个 \(1\times 2\) 的位置是否放格子。

接下来就要考虑怎么填往下放的格子。容易发现这一定是已经填了的格子的状态的补集的子集。那么先令 \(f_S\leftarrow f_{\complement_U S}\),然后做一遍高维后缀和。

答案即为 \(f_{n,0}\)

然后考虑怎么同时处理一行的信息。我们肯定是要从前往后和从后往前都要做一遍 DP 然后合并起来。设从前往后做得到的 DP 值记在 \(f\) 数组里,从后往前做得到的 DP 值记在 \(g\) 数组里。

那么对于第 \(i\) 行,我们设 \(f1_*=f_{i-1,*}\)\(f2_*=g_{i+1,*}\)

然后我们把这一行横着放的格子在 \(f1\) 中决策一遍,那么一个点 \((i,j)\) 的答案就是 \(\sum\limits_{j\not \in S\cup T,S\cap T=\empty}f1_S\times f2_T\)

\(f2\) 做一遍高维前缀和即可快速求出答案。

代码:https://pastebin.ubuntu.com/p/5sSJZ2yPnV/

[洛谷 P8434]「WHOI-2」D&D(高维后缀和 + 双指针 + 前缀和优化 DP)

题面

这个“装饰子集” 的定义其实就是说,一个数在一个集合的“装饰子集”里面,当且仅当这个集合中没有包含它的数。其中包含定义为:如果 \(a\) 包含 \(b\),那么 \(a|b=a\)

接下来有一个很强的结论:所有合法方案中,每个子串的“装饰子集”一定都等于原串的“装饰子集”。

证明如下:

  • 如果有一个数 \(x\) 在原串的“装饰子集”中,且不在所有子串的“装饰子集”中,就说明在每个子串中都有包含 \(x\) 的数。这个时候 \(x\) 就不应该出现在原串的“装饰子集”中,矛盾。
  • 如果有一个数 \(y\) 在所有子串的“装饰子集”中,且不在原串的“装饰子集”中,那么在每个子串中都没有包含 \(y\) 的数,因此原串中肯定也没有包含 \(y\) 的数,\(y\) 就应该出现在原串的“装饰子集”中,矛盾。

所以,我们可以先用高维前缀和求出原串的“装饰子集”,再考虑怎么计数。

\(f_i\) 表示将前缀 \(1\sim i\) 分成若干段,每段都包含所有原串的“装饰子集”中的数的方案数。

那么设 \(l_i\) 为最大的数满足 \([l_i,i]\) 中存在所有原串中“装饰子集”的数,就会有 \(f_i=\sum\limits_{j=0}^{l_i-1}f_j\)

\(l_i\) 可以使用双指针快速求出,DP 过程可以前缀和优化。

代码:https://pastebin.ubuntu.com/p/4HhjcKG7Tx/

[PKUWC2018] 随机算法(状压 DP)

题面

看到 \(n\) 很小,想到状压。

\(f_s\) 表示已经当前独立集已经选了所有集合 \(s\) 中的点的概率,\(g_s\) 表示 所有 \(s\) 中的点 和 与这些点有边相连的点 的并集。

那么对于在 \(g_s\) 中的点,它们接下来无论怎么放都不会对答案有影响,因此我们只需要考虑会对当前独立集产生影响的点。不难发现这些点有 \(|U-g_s|\) 个,其中 \(U\) 为全集。

转移有:\(f_s\times\frac{1}{|U-g_s|}\to f_{s\cup\{u\}}(u\not\in g_s)\)

代码:https://pastebin.ubuntu.com/p/jtB3Dt9gyS/

[CF1254E] Send Tree to Charlie(并查集 + 组合计数)

题面

一道比较容易有思路的题,但是细节似乎有亿点多……

考虑对于一个 \(a_i\ne 0\),将 \(a_i\to i\) 的路径拿出来,我们就相当于要求:\(a_i\) 到下一个点的边是 \(a_i\) 的所有连边中的被删去的第一条边;\(i\) 到上一个点的边是 \(i\) 的所有连边中被删去的最后一条边;这条路径上相邻两条边在所有和它们的公共点相连的边中的删除顺序也是相邻的(即:对于相邻三个点 \(u\to v\to w\),对于点 \(v\) 来说,\(u-v\) 这条边一定是在 \(v-w\) 这条边的前面一个被删除的),我们将每条边看成点,将其与后面一条边对应的点连边。

那么对于每个点 \(x\) 来说,把所有和它相连的边拿出来,设它们经过上面的处理之后形成了 \(cnt_x\) 条链(如果形成了环肯定无解),\(f1_x\) 表示 \(x\) 是否有已经确定的要删去的第一条边,\(f2_x\) 表示 \(x\) 是否有已经确定的要删去的、和第一条边不同的最后一条边,那么答案就是 \(\prod(cnt_x-f1_x-f2_x)!\)

判断无解的条件就是:\(\exist i\ne j,a_i=a_j\);连边连出了环(可以用并查集判断);要删去的第一条边和最后一条边属于同一条链,且形成的链数 \(>1\)(并查集 + set 判断)。

代码:https://pastebin.ubuntu.com/p/9ydf8nQTRk/

[CF1750E] Bracket Cost(分治 + 排序)

题面

对于一个区间来说,设 \(u\) 表示失配的左括号数量,\(v\) 表示失配的右括号数量,那么操作代价就是 \(\max(u,v)\)

证明:删掉所有匹配的括号之后,失配的括号一定形如 )))))((((((,进行 \(\min(u,v)\) 次右移操作,再补上 \(|u-v|\) 个括号就行了。

考虑分治,假设当前分治到的区间是 \([l,r]\),考虑如何计算所有跨过中点 \(mid\) 的区间的代价之和。

\(L_{x}\)\(R_x\) 分别表示 \([x,mid]\) 这一段区间做括号匹配后失配的左括号和右括号数量,\(L_y\)\(R_y\) 分别表示 \([mid+1,y]\) 这一段区间做括号匹配后失配的左括号和右括号数量。

那么对于区间 \([x,y]\) 来说:

  • 其失配的左括号数量为 \(L_x+L_y-\min(L_x,R_y)\),失配的右括号数量为 \(R_x+R_y-\min(L_x,R_y)\)
  • 分别化简一下式子:
    • \(L_x+L_y-\min(L_x,R_y)=L_x+L_y+\max(-L_x,-R_y)=L_y+\max(0,L_x-R_y)=L_y-R_y+\max(R_y,L_x)\)
    • \(R_x+R_y-\min(L_x,R_y)=R_x+R_y+\max(-L_x,-R_y)=R_x+\max(R_y-L_x,0)=R_x-L_x+\max(R_y,L_x)\)
  • 所以区间 \([x,y]\) 的代价就是 \(\max(R_y,L_x)+\max(L_y-R_y,R_x-L_x)\)

对于前后两项分开计算。排序后计算出另一边值比它小的数的数量,乘上贡献即可。

代码:https://pastebin.ubuntu.com/p/QMGkGvjspY/

posted @ 2022-11-07 14:54  csxsi  阅读(68)  评论(0编辑  收藏  举报