10月模板清理
20241016
记 \(f[i][j]\) 表示把 \(s[i \sim j]\) 变成回文,最少补几个,从 \(f[i][j - 1], f[i + 1][j], f[i + 1][j - 1]\) 三种情况转移过来即可。感性理解一下这样的状态定义是有最优子结构的。
肯定可以区间 \(dp\),再注意到状态的转移和上一步有关,所以记 \(f[i][j][0/1]\) 表示 \(H[i \sim j]\) 的方案数,其中 \(0\) 表示最新的一个人插进了队形左端(即 \(H[i]\)),\(1\) 就是右端(即 \(H[j]\)),根据大小关系转移即可。
唯一值得注意的是此题可以倒推,并且倒推之后的答案统计是 \(O(1)\) 的。正推的答案统计是 \(O(n)\) 的。
一个很朴素的想法是把两条路的位置同时都实时记录下来,这样就好转移了,事实证明是可以的。
记 \(f[i][j][p][q]\) 表示第一条路走到了 \((i, j)\),第二条路走到了 \((p, q)\) 的最大权值和。它可以转移到四种情况,即:
- 第一条路 往右走, 第二条路 往右走。
- 第一条路 往右走, 第二条路 往下走。
- 第一条路 往下走, 第二条路 往右走。
- 第一条路 往下走, 第二条路 往下走。
分 点重合/不重合 两种情况赋予转移代价,套上 \(BFS\), 直接转移就是 \(O(n^4)\) 的,当然需要标记一下每个点是否访问过。
考虑优化成 \(O(n^3)\),注意上面的方法两条路走的步数始终相同。记 \(f[i][j][k]\) 表示两条路都走了 \(i\) 步,第一条路处于第 \(j\) 行,第二条路处于第 \(k\) 行,最大权值和。起点是 \((1, 1)\),因此 \((x - 1) + (y - 1) = i\), 则 \(y = i - x + 2\)。最后特判一下只走了 \(1\) 步的情况就行,因为不满足这个式子。
学到一个 \(trick\) : 区间相关的环形问题,可以把序列复制一遍接在后面,然后只推到 \(len = n\) 就可以了,最后取 \(\max_{i \in (1, n + 1)} f[i][i + n - 1]\)。这样比 \(n\) 次区间 \(dp\) 快很多,因为不同的断点位置下的区间可以使用相同的一段很小的区间。
最大子段和 + 前后缀max 连一下就可以。
一个最大独立集问题,记 \(f[u][0/1]\) 表示选 \(u\) 或者不选 \(u\),对于一个儿子 \(v\),有转移:
树上背包,记得给 \((u, v)\) 留一条边就可以了,并且一定要连这条边,不然转移没有意义。
只有 \(n\) 能进行状态压缩。一开始的想法是记 \(f[i][S]\) 表示前 \(i\) 个操作,强制选 \(i\),然后得到了 \(S\) 这个集合,最少操作数。然后还“机智”地发现开个 \(vector[i]\) 表示 \(f[i][?]\) 有哪些更新出来了。
然而无济于事,因为此题 \(dp\) 的顺序并不是简单的递推(会有后效性),因此采用记忆化 \(BFS\) 进行实现。
答案: \(min(f[i][0])\)。
初始化: \(f[0][(1 << n) - 1] = 0\),其他初始化为 \(inf\)。
式子就是 \(f[i][S] = min(f[j][T] + 1, f[i][S])\), \(T\) 的话就照着题目要求根据 \(S\) 推出来。
时间复杂度为 \(O(2^nmn)\)。
最后发现其实当前具体在哪个操作并不重要(下一步转移都是向全部操作),所以可以优化成一维。并且由于广搜良好的性质找到一个 \(f[0]\) 就直接输出就可以!
状态压缩DP技巧 - A Simple Task 「CF11D」
一开始想成树形dp了,然后发现非常的难写,因为点之间有各种路径计数,很难补充不漏。每次扩展和前一个状态的联系也不是很大。
题解是一种巧妙的状压dp。显然可以对已选点集进行状压。先考虑如何统计环的数量,对于 \(i \to j\) 的一条简单路径,若路径外一点 \(k\) 同时和 \(i\),\(j\) 有连边,那么就成了一个环。基于此,我们可以转化成利用 \(dp\) 统计简单路径的数量,而统计答案时在另找一个点连环。(状压没想到,但这一步转化想到了……)
这样定义出来的状态是 \(f[S][i][j]\),表示选择的点集为 \(S\), \(i \to j\) 的简单路径的数量。转移比较好写。空间复杂度会达到 \(2^{19} \times 19^2 = 189,267,968\),存不下,时间应该还要再乘一个 \(19\), 也爆掉了。
考虑怎么优化时空。注意到后两维其实都是我们主动加的限制,但仅仅是为了统计答案而同时维护这两维显得有些不划算。能不能利用好 \(S\) 中包含 \(i, j\) 的性质进行一些优化呢?
考虑强制规定 \(lowbit(S)\) 那一位是起点,\(i\) 作为终点,这样只要遇到一个满足 \((1 << i) = lowbit(S)\) 的 \(i\), 就统计答案。
状态: \(f[S][i]\),意义如上。
答案: \(ans = \sum_{lowbit(S) = 2^i}f[S][i]\)。最后记得 \(ans = (ans - m)/2\), 因为会误算 一条边和两个端点的“假环”。
初始化: \(f[2^i][i] = 1,i \in [0, n - 1]\)。
转移:
20241017
数位dp经典题。分两步求解:
- 预处理 \(f[i][j]\),表示有 \(i\) 位,最高位填 \(j\),方案数。
- 记 \(dp(x)\) 表示 \(1 \sim x\) 有多少个 \(Windy\) 数,则答案为 \(dp(b) - dp(a - 1)\)。
记 \(a[1 \sim cnt]\) 表示 \(x\) 的每一位(从高到低),那么每一位的转移分 \(now \lt a[i]\) (后面的位随便填,可直接调用 \(f\) 统计答案)和 \(now = a[i]\) (继续往下走)两种情况转移。走到 \(i = 1\) 记得 \(ans\) 还要额外 \(+1\),就是这个数本身的答案。
20241018
可以是数位dp,也可以是高维dp……
- 还是先说一下我超级复杂的容斥做法吧!
记 \(f1\) 表示 相邻 2/1个 数相同,数的个数。
\(f2\) 表示 相邻 2/1个 数相同,且同时出现 4 和 8,数的个数。
\(f3\) 表示 同时出现 4 和 8,数的个数。
则答案为 \((r - l + 1) - (f1 + f3 - f2)\)。
写三个 数位dp预处理 和 三个数位统计就可以啦!
一看题解感觉自己……(哎至少是自己亲手做出来的一道蓝题!)
-
正解是记 \(f[x][last1][last2][f4][f8][flag]\) 表示 \(x\) 位数,上一个数是 \(last1\),上上个数是 \(last2\),是否出现 4,是否出现 8,是否有数字连续出现 3 次,数的个数。
转移比较典型,分 \(i < a[x]\) 和 \(i = a[x]\) 两种情况转移。
注意这里我们要求的是至少要连续出现 3 次了,和我上面的做法是反着的。
20241023
数位DP - Round Numbers 「USACO06NOV」
首先读题读对:二进制下数位dp
还是很典的数位dp,先写预处理,再写 \(dp(x)\),最后 \(dp(r) - dp(l - 1)\)。
一开始我的定义是 \(f[i][j]\) 表示 \(i\) 位数,含有 \(j\) 个 \(0\) 的圆数个数。由于包含了前导零的情况,转移容易算重,非常不好写……
考虑增加一位区分有没有前导零,记 \(f[i][0/1][k]\) 表示 \(i\) 位数,最高位是 \(0/1\),含有 \(k\) 个 \(0\) 的圆数个数。接着来考虑贡献组成:
设当前求解的数为 \(x\),有 \(cnt\) 位。
- \(Case\ 1\):含有前导零,贡献为
- \(Case\ 2\):不含前导零,最高位就强制填 \(0\)。从高位到低位统计,若 \(a[i] = 0\),直接过;若 \(a[i] = 1\),可以选择填 \(0\) 然后算贡献。
(数位dp终于做完了……)
由于空间跑路器的存在,直接跑最短路再 \(popcount\) 是错滴。
看到 \(2^k\),想到这种题应该和倍增有关系。
考虑一个情景:
- 如果 \((i \to k)\) 和 \((k \to j)\) 都有一条长为 \(2^0\) 的路,那么 \((i \to j)\) 就有一条长度为 \(2^1\) 的路!然后用跑路器就可以 \(1s\) 跑完,即 \(i, j\) 之间存在一条权值为 \(1\) 的边。
这个情景告诉我们,如果我们能知道 哪些点之间能用跑路器,即 “存在 \(2^k\) 的路径” ,并给他们在新图上连一条权值为 \(1\) 的边,就差不多做完了。
现在考虑怎么求 “点之间存不存在这种路径”。
有向图概率期望dp。
这其实是 \(DAG\) 上\(dp\)。转移顺序就确定了。
然后就是期望 \(dp\) 的部分。期望 \(dp\) 很多状态都是倒着定义的,形如 “当前在某个中间状态 \(i\),要到终止态 \(n\) 还需要的期望值”。
此题就是 \(f[u]\) 表示当前到达了 \(u\),走到 \(n\) 还需要的期望步数。
因为转移顺序中没有回头路可走,所以不用考虑自转移的情况。
\(d[u]\) 是 \(u\) 的入度。
“自转移” 参见这道题:百事世界杯之旅-luogu
因为说了是有向图,所以感觉就很能dp,但是不是 \(DAG\),遇到环就dp不了了。
再思考,进了一个环肯定要把贡献全部拿完才会出来,再根据“恢复系数”,可以预处理每个环的贡献,最后环缩点。
缩点后整张图就变成一张 \(DAG\) 了,就可以愉快的dp了。
最后注意因为 \(s\) 不一定是入度为0的点,所以要么 先一次 \(dfs\) 把涉及的子图全部跑出来,再 \(topsort\);要么直接 \(bfs\),每个点可能重复入几次(没被卡时间)
时间复杂度瓶颈在算环的贡献,最坏情况是 \(O(mlog_p{\frac{1}{w}})\),\(p\) 是恢复系数,\(w\) 是初始蘑菇数。
设 \(f[i][j]\) 表示走到 \((i, j)\),最大收益。
感觉既可以 \(st\) 表,也可以单调队列优化。\(2000ms\)嘛,\(O(n^2logn)\) 能卡。
考虑一组 \(a \lt b\), \(f[i - 1][a] \lt f[i - 1][b]\),那么 \(a\) 就永远无法成为贡献点了,因为位置不如 \(b\) 优,而且值也没有 \(b\) 大。
由此,我们需要维护一个 \(f[i][j]\) 递减的单减栈,队尾维护单调性,队首清理过期元素即可。
20241024
考虑已知跨步的范围后怎么做。记 \(f[i]\) 表示跳到第 \(i\) 个格子,最大分数,容易写出转移:
显然 \(g\) 可以二分,直接做是 \(O(n^2logn)\) 的。
考虑单调队列优化dp,维护一个 \(f[i]\) 单调递减的单调栈。
一个细节是若 \(d[i + 1] - d[i] \lt l\) 是不能直接把 \(i\) 加入的,要记一个 \(nw\) 表示现在加到哪一个了,每次判断距离是否 \(\ge l\) 了。总之细节比较多。
自己写过之后看了一下别人的代码,发现很多东西可以合并。比如 \(nw\) 从 \(0\) 开始而不要从 \(1\) 开始,且当 \(head \le tail\) 时才更新 \(f[i]\),这样开头涉及 \(0\) 的部分就不会出错了。代码也简洁多了。
并且单调队列的过期清理应该也只需要一次,放在插入元素之后搞一次。如果 \(f[nw]\) 真的在 \(f[q[tail]]\) 处遇到了这些过期元素,那也是没问题的,因为要么就是 \(f[nw] >= f[q[tail]]\),这些东西本来就会弹出去。要么就是 \(f[nw] < f[q[tail]]\),那之后过期清理也就对了。
20241025
发现自根往下选并不好决策,考虑倒着做。
定义:记 \(f[i][j]\) 表示 在 \(i\) 的子树内,选的总体积为 \(j\) 且必选 \(i\),最大价值。
答案:\(\max_{1 \le i \le vmax}f[root][i]\)
转移:\(f[i][j] = \max_{j - k \ge a[i]}f[i][j - k] + f[son[i]][k]\)
初始化:\(f[i][a[i]] = b[i]\),其他的设为 \(-inf\)。
void dfs(int u){
f[u][a[u]] = b[u];
for(auto v : G[u]){
dfs(v);
G(i, vmax, a[u]){
G(j, i, a[v]){
if(f[u][i - j] >= 0 && f[v][j] >= 0)
f[u][i] = max(f[u][i], f[u][i - j] + f[v][j]);
}
}
}
}

浙公网安备 33010602011771号