CF DP 题目思路整理
CF1592C
考虑k=2。
- 如果总异或和为0,任意删。(可以反证)
- 否则无解了(可以反证)
没思路?
考虑k=3。
- 你画一下这三个连通块,设其分别异或和为s。我们可以把两个合并一下,异或和为0,这样我们发现,对于剩下的那个连通块,s一定等于总异或和。
故就是找三个连通块,分别异或和等于总异或和s。
应当注意的是这个k=3你是不会做的,贴代码
//xorsum代表子树异或和,s代表总异或和,cnt代表子树中能被可割且不相交的个数
void dfs(int u, int fa) {
xorsum[u] = a[u];
for (int i = h[u]; i; i = ne[i]) {
int j = e[i];
if (j == fa) continue;
dfs(j, u);
xorsum[u] ^= xorsum[j], cnt[u] += cnt[j];
}
//考虑合并子树,看看合法?子树两个or子树1个加当前
if (cnt[u] >= 2 || (cnt[u] == 1 && !xorsum[u])) flag = true;
if (!cnt[u] && xorsum[u] == s) cnt[u] = 1; //当前贡献一个
}
没思路?
考虑k=4。magic出现了!设其x,你考虑把两个合并,变成0,再合并一个,变成x,这样就是找一条边的情况!也就是说,存在四个连通块,首先应该存在两个连通块。
k=5,应该先存在3个连通块。
...
于是就做完了,只需要考虑k=2,3的情况。
CF1805D
- 我没看题解,比如说先拿特殊数据找规律,比如一条链,或者k很小,k很大。跟距离有关,有可能可以从直径的视角分析。 \(---lester\)
题面乍一看好像没什么头绪,不妨先按题意模拟一下过程。
- 模拟:
刚开始 \(k=1\),\(G_k\)里任意两点之间都相连,此时整个图就是一个 SCC。
\(k\) 增大到 \(2\),长度为 \(1\) 的边断了,剩下的边还保留着。这时候可能有一些点脱离“大部队”,\(SCC\) 数量可能会增加。\(k\) 不断增大,一直到 \(k=n\) 时,显然所有边都断了,每个点都是孤立的 \(SCC\)。
可以看出,\(k\) 从 \(1\) 到 \(n\) 的整个过程,整个图逐渐在断边,有越来越多的点脱离“大部队”,\(SCC\) 的数量逐渐增加,直到每个点都是一个 \(SCC\)。
- 考虑条件:
现在假如我们知道这个“大部队”是什么,对于每个点,它是怎么脱离“大部队”的呢?可以发现,当 \(k\) 比它到“大部队”的最远距离还要大,那么这个点到“大部队”的所有边全断,也就脱离出来了,否则一定在大部队里面,因为你考虑两个点,一定可以通过最远的那个点连通。
题目给出的是一棵树,联想到一条和最远距离相关的性质:对于树上任意一点,其余点中离这个点距离最远的点,一定是(我钦定的一条)树的直径的端点。
由此可以发现,这个“大部队”其实就是树的一条直径,那么这题就做出来了:找到一条直径的两端,和其余点到两个端点距离的最大值,当 \(k\) 大于这个值,\(SCC\) 数量增加 \(1\)。
CF1275D
- 很容易注意到一天要打最多的怪。
- 接着考虑在一天中当前打到第 \(l\) 个怪(未打),将打掉到第 \(r\) 个怪,可行否?<-(其实这步我是想到的)
- 对怪物, 若存在英雄 \(i\),使得 \(s_i \geq r-l+1\) 且 \(p_i \geq \max_{i=l..r}{a_i}\),则可行。(充要)
- 到这里其实可以做了呢,我们把英雄按 \(s\) 排个序,我们二分这个 \(r\),那相当于在一个后缀里面查\(p_i\) 最大值。
- 要得到 \(O(n)\) 算法,我们还得动点脑子。
- 既然可以二分答案,那能不能增量式?因为查询不依赖 \(l\),那其实这个过程是非常重复的,那我们可以预处理出 \(bst_r = \max_{i=r..n} p_i\),对每天从 \(l\) 开始循环到可行条件不成立即可。
CF1781D
求差求和、套用公式是常用的构造技巧。
- 考虑完一个的答案后,我们考虑两个的条件,因为如果存在三个的方案,一定存在两个的方案。
- 如果说两个的方案不多,我们可以枚举。实际上也是这么做的。
声明一下:我们设 $ g(x) $ 为 $ a_1 + x, a_2 + x, \cdots, a_n + x $ 中完全平方数的个数。
- 答案至少为 1,这里不再证明。
- 如何让答案更大(至少等于 2):
- 枚举两个下标 $ i \ (i \leq n) $ 与 $ j \ (j \leq n) $,其中 $ i < j $
- 找出 $ x $ 与 $ a_i $ 和 $ a_j $ 的关系
设:\(a_i + x = p^2 \quad\) 与 \(\quad a_j + x = q^2\)
两式相减得:
$ a_j - a_i = q^2 - p^2 = (q - p)(q + p) $
设 \(q - p = d\)(\(d\) 是 \(a_j - a_i\) 的因数),可得方程组:
解得 $ p $ 和 $ q $:
关键推导:求 $ x $
\(x\) 数目不多,最后枚举所有可能的 $ x $ 计算 $ g(x) $ 即可!
CF1720D1
-
\(x - y, y - x \leq x \oplus y \leq x + y\)。
-
\[dp_i = \max_{0 \leq j<i}{([a_j \oplus i < a_i \oplus j]f_j)+1} \]
优化:
- \(a\) 的范围只有200。要玩点性质。
- \(x - y, y - x \leq x \oplus y \leq x + y\)
- \(i - a_j < a_i + j\)
- \(i - j < a_i + a_j \leq 400\)
做完了。
CF734C
魔法魔法。不超过一次,直接枚举吧!考虑怎么优化到只枚举一个,那由于另一个单调,双指针就做完了。
- 扩展的是,哪怕另一个没有单调性,也能用类似单调队列的样子踢掉,不单调一定不优。
- 如果能使用多个魔法,那就是背包问题了。
CF1286A
dp是容易的。\(dp_{i, j, k}\) 代表前 \(i\), 填了 \(j\) 个奇数,当前填的奇偶。\(n^2\)
这个贪心得好好看看。\(nlogn\)
-
需要填的是一段一段(按已经有的划分)。
-
分讨:如果这一段两头奇偶性不同,那最好一定是 \(1\)。原因是我可以把奇的归到奇数那边,偶的归到偶数那边,对任何方案都成立。
-
如果这一段两头奇偶性相同,那要么 \(0\),要么 \(2\)。原因是要么全填与两端一样奇偶的,要不然也填奇也填偶,归到一边后贡献一定最好是 \(2\)。
-
那我们只能决策第二种情况,我们应该首先能填就填,容易发现是按长度贪心就可以啦!
-
序列的两头呢?这个你也不会。暴力枚举两端奇偶,就不用讨论边界了。
int res = 1e9;
for (int i = 0; i < 2; i ++ )
for (int j = 0; j < 2; j ++ )
a[0] = i + 2, a[n + 1] = j + 2, res = min(res, solve());
- 可以用计数排序做到 \(O(n)\),但是没有必要了。
CF1401D
\(\sum\limits_{i=1}^{n-1} \sum\limits_{j=i+1}^n f(i,j)\)
直接变成对一条边而言的权重 \(siz_u * (n - siz_u)\),然后贪心的放质因子。注意不要取模。调的太久了。
代码
CF161D
- 树上距离为 \(k\) 的点对数目。可以淀粉质。
- 直接 \(DP\) 也可以哩!
void dfs(int u, int fa) {
f[u][0] = 1;
for (int i = h[u]; i; i = ne[i]) {
int j = e[i]; if (j == fa) continue;
dfs(j, u);
for (int k = 1; k <= m; k ++ )
f[u][k] += f[j][k - 1];
}
ans += f[u][m];
for (int i = h[u]; i; i = ne[i]) {
int j = e[i]; if (j == fa) continue;
for (int k = 0; k <= m; k ++ )
ans += (LL)(f[u][m - k] - f[j][m - k - 1]) * f[j][k - 1];
}
}
int main {
cout << ans / 2 << endl;
}
CF279C
- 单峰,单谷可以考虑维护 对于每个点左端大于等于,右端大于等于的个数。
CF1517D
- 一开始想floyd,由于图稀疏了点,可以直接这么转移啦。
CF1955F
代码最短的的绿题了。
- 题意:给定一个数字 \(n\) 和 \(n\) 个序列中数字 \(1\),\(2\),\(3\),\(4\) 的个数,每个序列以最佳方式(保证下文总和尽可能大)每次取出序列中的 \(1\) 个数字直至该序列为空,每个序列求每次取出后所有数字的异或不为 \(0\) 的次数总和。
- \(4\) 比较特殊,先把他讨论掉。那如果他是奇数,我一定要先做一次,要不然答案更劣。这样变成偶数,那其实可以都做完了再处理他,这是最优的。贡献 \(p_4 >> 1\)
- \(1, 2, 3\) 若奇偶性不同,那假设单独的那个是奇,那直接减去,假设单独的是偶,那需要对其他两个减 \(1\),否则一定不优。好了这样我们就处理到三个奇偶性相同了,要么他们都减 \(1\),代价 \(3\),要么对其中一个减 \(2\),代价 \(2\),显然这更优。那每减 \(2\),就贡献 \(1\)。 \((p_1 >> 1) + (p_2 >> 1) + (p_3 >> 1)\)。考虑最后,若 \(3\) 个 \(1\),还需加 \(1\)。
- 这样其实就是最优的,通过最优决策分析(
- 也可 \(dp_{i, j, k}\) 代表 \(i\) 个 \(1\), \(j\) 个 \(2\), \(k\) 个 \(3\) 从而求解。
CF1535D
前面与 \(CSP-S \ 2024 \ T4\) 很类似。疑似这个题加强变难版。这个建树应该好好掌握。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
using namespace std;
const int N = 1000010;
int n, m, q;
int f[N]; //点上哈
char s[N];
void update(int u) {
if (s[u] == '0') f[u] = f[u << 1 | 1];
if (s[u] == '1') f[u] = f[u << 1]; //子节点编号反过来了
if (s[u] == '?') f[u] = f[u << 1] + f[u << 1 | 1];
}
void build(int u, int l, int r) { //l~r获胜的,存在在u中
if (l == r) { f[u] = 1; return; }
int mid = (l + r) >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
update(u);
}
int main() {
scanf("%d", &m); n = (1 << m) - 1;
scanf("%s", s + 1);
reverse(s + 1, s + n + 1);
build(1, 1, n + 1); //为什么加一呢,由于有2^m个叶子
cin >> q;
while (q -- ) {
int p; char c[2];
scanf("%d%s", &p, c); p = n + 1 - p; //这里好理解,画个图看看
s[p] = c[0];
while (p) {
update(p);
p >>= 1;
}
cout << f[1] << endl;
}
return 0;
}
CF1442A
- 操作是区间减,最后要把序列变成 \(0\)。这样就想 差分。
CF1979D
字符串hash。
此题还有贪心官解。但我没看懂。
CF339C
这个题注意哈。你首先想 \(dp_{i,j,k,t}\) 代表 前 \(i\), 砝码质量分别是 \(j,k\), 上一个是 \(t\),是否可行。
这肯定是过不去的。这个 \(trick\) 可以记一下,比较常见:
- \(dp_{i,diff,t}\) 只记录两砝码差值 \(diff\)。
CF1446B
很那啥了,不知道这种题目自己为什么没做出来,我猜是看到 \(LCS\) 就以为往上面走了,但其实就是状态相似:
- \(dp_{i,j}\) 代表 \(s_i, t_j\) 结尾的最大贡献。
- 若 \(s_i=t_j\), \(dp_{i-1,j-1}+2→\)
- 否则,\(max(dp(i-1,j) - 1,dp(i,j-1) - 1, 0) →\)
CF869C
这个计数,你首先要考虑的是限制是什么。。然后还有一个枚举的计数技巧。
拿此题来说,相当于:
- 同颜色不能相互连边
- 两种颜色扣出的图是匹配
除此之外没有限制了。那其实就三种连边方式:\(A→B,B→C,C→A\)。那考虑 \(A→B\):
- 枚举 \(1,2,3...min(a,b)\) 个点的情况, 则 \(\sum_{i=0}^{min(a,b)} C_a^i *C_b^i * i!\)
另解:DP
设 \(f(i,j)\) 为在数量为 \(i\) 的颜色的岛与数量为 \(j\) 的颜色的岛之间连边的方案数。比如,\(f(1,1)=2\),因为我既可以选择不连边,也可以选择连一条边。那么,答案就是 \(f(a,b)×f(b,c)×f(a,c)\)。
考虑 \(f\) 的递推式。明显的,同一个点只能连出去一条边,也可以不连边。如果我连边,则要在 \(j\) 个点中选一个点和它相连,有 \(j\) 种可能性,而 \(i\) 要减一,\(j\) 也要减一。如果我不连边,则只有一种可能性,\(i\) 减一,\(j\) 不变。于是我们得到了方程。
\(f(i,j)=f(i−1,j−1)×j+f(i−1,j)\)
边界条件明显是 \(f(i,0)=f(0,i)=1\)。前面的式子直接递推即可。时间复杂度在默认 \(a,b,c\) 同阶的情况下是 \(O(a^2)\)
CF1221D
序列左右各不等的处理方案,即 \(a_i \neq a_{i-1}\)。
- 观察:任意位置上的数增加值 \(Δx=0,1,2\)
- 无脑 \(DP\)。
CF1552D
非常好的观察题。下面是我复制的题解。其实你拿到这个题,第一反应是 \(b\) 多一个就好了,那现在实际上就是要缩掉这一个,考虑什么时候能缩掉,那其实就是存在一个子集合加和为 \(a_p\),和下面描述等价。
我们考虑一个图论模型,如果 \(( b_j - b_k = a_i )\),那么连 \(((j, k))\) 为一条无向边。如果图中出现了一个长度为 ( \(n\) ) 的链 \(( x_1 \leftrightarrow x_2 \leftrightarrow \cdots \leftrightarrow x_n )\),我们一定可以确定 \(( x_1 )\) 和 \(( x_n )\) 之间的关系。换句话来说,在这张图中,已经无法人为的添加任何一条边,否则都有可能会与现有条件产生冲突。
然而现在题目就是构造基环树,考虑加入一条边不会冲突当且仅当它所在的环境没有产生冲突,其它的条件都不会产生冲突。返回定义看什么时候产生环,即:\(( b_{x_1} - b_{x_2} \in a, b_{x_2} - b_{x_3} \in a, \ldots, b_{x_k} - b_{x_1} \in a )\)。
因为 \(( b_{x_1} - b_{x_2} + b_{x_2} - b_{x_3} + \cdots + b_{x_k} - b_{x_1} = 0 )\),所以题目转化为:对 \(( a )\) 的每个元素任意取正负,是否存在一个 \(( a )\) 的子集和为 \(0\)。
CF1843F1
关键:有一棵节点带权的树,权重只可能为 \(1\) 或者 \(−1\),存在一个可以为空的子路径的权重和为 \(k\) 等价于 \(k\) 在两点之间可能取到的最大和最小权值。
CF1525D
状态前缀化是一个不错的手段,其实我做法没有前缀化,前缀化后转移方程非常简单。
CF429B
当题目中出现 \(1,2\) 这种数据,你应当留意是不是要讨论或者枚举。
这道题,就是直接枚举相交点。
CF1902D
这个题没看题解一发过啊。取反这个操作很强,你应该从讨论看看下手。
最后应该是有个问题:在 二元组序列的 \([l, r]\) 中是否存在 \((x, y)\)。我应该是搞了三元组排序做的。
其实可以 map<pair<int, int>, vector<int>>, 嗯这样就在 \(vector\) 里二分就行了。
一个常见模型:\(x_{i + 1} = (x_i + k) \mod n\)
- 有周期性。$$x_i = x_{i + T}, T = \frac{n}{gcd(n, k)}$$
证明:
那接下来的工作就是引入公因数:
由于 \(gcd(k', n') = 1\):
如果你想证明最小性,反证。
- 同余性质(自己拿数学归纳法证就行):
- 感性理解整个序列:$$1, 1 + d, 1 + 2 * d....$$
CF1267E
这个题是上个结论的应用题。你考虑枚举答案 \(n\),这样问题就变成了:有若干相等的块(大小 \(\frac{n}{d}\)),共有 \(d\) 个,那问你这一堆颜色能不能安放。
这是容易的。


浙公网安备 33010602011771号