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. 答案至少为 1,这里不再证明。
  2. 如何让答案更大(至少等于 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\) 的因数),可得方程组:

\[\begin{cases} q - p = d \\ q + p = \dfrac{a_j - a_i}{d} \end{cases} \]

解得 $ p $ 和 $ q $:

\[\begin{cases} p = \dfrac{1}{2} \left( \dfrac{a_j - a_i}{d} - d \right) \\ q = \dfrac{1}{2} \left( \dfrac{a_j - a_i}{d} + d \right) \end{cases} \]

关键推导:求 $ x $

\[x = \dfrac{1}{4} \left( \dfrac{a_j - a_i}{d} - d \right)^2 - a_i = \dfrac{1}{4} \left( \dfrac{a_j - a_i}{d} + d \right)^2 - a_j \]

\(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)}$$
    证明:

\[x_i = x_{i + T} \]

\[x_i \equiv x_i + T * k \mod n \]

\[T * k \equiv 0 \mod n \]

那接下来的工作就是引入公因数:

\[T * k' * d \equiv n' * d \mod n \]

由于 \(gcd(k', n') = 1\)

\[\frac{n}{gcd(n, k)} | \ T \]

如果你想证明最小性,反证。

  • 同余性质(自己拿数学归纳法证就行):

\[x_i \equiv x_1 \mod d \]

  • 感性理解整个序列:$$1, 1 + d, 1 + 2 * d....$$

CF1267E

这个题是上个结论的应用题。你考虑枚举答案 \(n\),这样问题就变成了:有若干相等的块(大小 \(\frac{n}{d}\)),共有 \(d\) 个,那问你这一堆颜色能不能安放。
这是容易的。

posted @ 2025-05-21 17:18  hhhhhua  阅读(12)  评论(0)    收藏  举报