2024.11 做题记录
一个月封闭集训,可能题会稍微多一点。
001. P8940 [DTOI 2023] C. 不见故人 提高+/省选-
先来分析一下题目:写的还是比较简洁的,就是把区间里的数都变为它们的
考虑动态规划,定义
如若
找到块是简单的但细节较多,怎样处理看个人喜好。定义
002. CF1292B Aroma's Search 1700
题目选自:2020, 2021 年 CF 简单题精选
题意就是它给你生成了一个点列
我们直接暴力生成这个点列,然后直接暴力枚举起点。这都是
然后在枚举起点的同时,直接暴力枚举左右端点,因为注意到,我们最多最多就只能折返一次,然后枚举是先向右走还是先向左走即可。时间复杂度
*003. CF1313D Happy New Year 2500
题目选自:2020, 2021 年 CF 简单题精选
题意很简洁,不需要我过多叙述。
我们先把有用的
接下来进入主体部分。利用
我们定义第
接下来考虑怎么转移,我们先记第
讨论第
- 若第
个操作点为起点,那么我们考虑怎么刻画这个事情。因为我们不考虑你起始操作点的顺序,因为你已经存了编号,所以你随便找一个没有被占用的位置占用上即可,即把 为 的一个点改为自己的 ,并用这个点的位置去进行更新。想到这个后转移就较为容易,枚举二进制串 到 。如果串 中存在 这一位,那么就是直接逆推,考虑前面没加上这一位时的答案,即 ,如果没有 这一位,那么也是简单的,就是 - 若第
个操作点为终点,其实与起点是类似的。因为这是终点,所以前面必会有一个点对其有影响且是它对应的起点。我们找到这个点后,它就对这段区间没有了影响,把它取消标记,也就是将其 值设置为 。同时记录位置 。那么转移也是非常简单的,如果串 中已经存在 这一位,那么就是不合法的,因为我们已经将第 位去除了,赋值为无穷大。否则
最后一步是统计答案,就是
004. CF1322B Present 2100
题目选自:2020, 2021 年 CF 简单题精选
题意简洁,不过多叙述。
非常妙的一道题。看到
有了第
005. P5094 [USACO04OPEN] MooFest G 加强版 普及+/提高
题目摘自:我的《分治全家桶》博客
非常魔怔的一道题,反正分治就很人类智慧的呢。这题感觉树状数组做法是绿,分治做法得有蓝,因为不太好想。题目问的是
看到绝对值我们考虑先把绝对值拆掉再说。按照键值
我们继续考虑后序遍历 CDQ 分治(或者说序列分治)。在递归计算完左右两个子区间的答案后,左右区间内部天然的
考虑我们合并要干什么东西。我们要处理的是 左右两边的
合并时,每次加一个左区间的
每次加一个右区间的
注意边界情况也要加上这两种贡献哦!不然你就会输出
006. P3403 跳楼机 提高+/省选-
同余最短路模板题,但是真的好高妙啊,谁能想到要这么做啊?
我们发现如果直接连边 SPFA 或者直接连边 Dijkstra 都会直接炸的死死的。我们只能另辟蹊径。
然后这道题我们就做完了。
007. P2365 任务安排弱化版 提高+/省选-
一眼能看出来是动态规划类型的题目。考虑朴素的动态规划算法。
这应该是简单的。定义
008. [ABC379F] Buildings 2 1659 蓝牌题
感觉真的不好做啊?可能是我对这两种技巧都太生疏了吧。
你发现如果你单纯想预处理出所谓的“最左边的能看到你的”,那说明你跟我一样读错题了。因为题目中定义的“看见”这一动作是不完全具有单调性的。但这还是启示我们使用单调栈算法。
我们反过来考虑“你能看见啥”。那么我们考虑倒过来使用单调栈。
我们考虑处理一组
我们分别处理
*009. [ABC379G] Count Grid 3-coloring 2304 黄牌题
打星是因为这题用到了一个状态压缩动态规划的经典优化 Trick——轮廓线状态压缩动态规划。
我们钦定
首先我们先预处理出
考虑怎样转移,因为对于每一个格子,能影响它的只有上边和左边两个格子,先把这两个格子的状态按照某种方式快速求出来。然后枚举这一格的状态
最后答案即为
010. [ABC377E] Permute K times 2 1685 蓝牌题
一道与置换有关的趣题。不是很难,但很有趣就是了。
首先考虑我们的熟知结论 Exercise G's Trick,如果轮换数组为一个排列,那么所有数都必然会在自己的置换环上行走。
考虑显然的先把所有的环搜出来,记录一下每个数字在哪个环上,环长,环上位置实际对应的数字,这都是简单的,可以一并记录。
考虑到
011. [ABC377G] Edit to Match 1782 蓝牌题
这场 G 是不是有点简单,这个字典树提示的其实有点明显了。首先数据范围给的是
然后肯定是前缀能取就取,后面的话就取一个最短的后缀。最短后缀就记一个数组,最后随便统计一下就做完了,这题是真水题了。
*012. P5999 [CEOI2016] kangaroo 省选/NOI-
连续段动态规划 / 插入类型动态规划 / kangaroo's Trick
非常强的一个动态规划技巧。常见的类型如有一个波浪形的限制,就像这道题一样。还有排列计数和不能重复用一些数这种限制。还有
首先我们不考虑起点
定义
- 增加一个连续段:
。因为原先有 个连续段,所以有 个空格可以插入。 - 合并两个连续段:
。原先有 个连续段,有 个空格可以供插入合并。
需要注意的是,因为数组天然有序,所以我不管怎么合并都是满足条件的。
下面考虑
同时前面的另起连续段部分也要变一变。因为在插入
然后这道题我们就做完了。
*013. P7967 [COCI2021-2022#2] Magneti 省选/NOI-
有了上一题的铺垫,我们这道题就是相对简单的。考虑 kangaroo's Trick,进行连续段动态规划。
考虑先按照
- 增加一个连续段:没啥好说的,就是
- 合并两个连续段:就是
,这也是简单的。 - 插入到已有连续段的两端:其实也不难,就是
, 的原因是连续段的两边都可以插入。
最后答案就是
于是这道题我们就做完了。
014. P8865 [NOIP2022] 种花 普及+/提高
补了补 NOIP2022,发现第一题还是比较水的,就非常常规。
首先考虑 'C' 字型怎么做,非常简单,预处理出右边最长
然后考虑 'F' 字型怎么做,稍微难一点点,考虑预处理出下面最长
然后我们就做完了,过于简单了。
*015. P3047 [USACO12FEB] Nearby Cows G 普及+/提高
历史遗留问题了属于是。大概是两年前剩的题了。现在依然不会,没有长进,恼!
考虑
首先我们可以一遍 DFS 求出
然后考虑怎样求出
小优化:注意到我们可以重复利用
016. P3919 【模板】可持久化线段树 1(可持久化数组)
学了可持久化线段树,这是板子。注意
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000005;
int n, m, A[MAXN], root[MAXN], Top = 0;
struct Seg
{
int L, R, val;
} Sgt[MAXN << 5];
int BuildTree(int u, int L, int R)
{
u = ++ Top;
if(L == R)
{
Sgt[u].val = A[L];
return u;
}
int mid = (L + R) >> 1;
Sgt[u].L = BuildTree(Sgt[u].L, L, mid);
Sgt[u].R = BuildTree(Sgt[u].R, mid + 1, R);
return u;
}
int Clone(int u)
{
Top ++;
Sgt[Top] = Sgt[u];
return Top;
}
int Update(int u, int L, int R, int x, int val)
{
u = Clone(u);
if(L == R)
{
Sgt[u].val = val;
return u;
}
int mid = (L + R) >> 1;
if(x <= mid) Sgt[u].L = Update(Sgt[u].L, L, mid, x, val);
else Sgt[u].R = Update(Sgt[u].R, mid + 1, R, x, val);
return u;
}
int Query(int u, int L, int R, int x)
{
if(L == R) return Sgt[u].val;
int mid = (L + R) >> 1;
if(x <= mid) return Query(Sgt[u].L, L, mid, x);
else return Query(Sgt[u].R, mid + 1, R, x);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> A[i];
root[0] = BuildTree(0, 1, n);
for(int i = 1; i <= m; i ++) // 更新版本
{
int rt, opt, x, y;
cin >> rt >> opt >> x;
if(opt == 1)
{
cin >> y;
root[i] = Update(root[rt], 1, n, x, y);
}
else
{
cout << Query(root[rt], 1, n, x) << '\n';
root[i] = root[rt]; // 这里也要新建版本
}
}
return 0;
}
017. P3834 【模板】可持久化线段树 2
权值可持久化线段树,先对权值进行离散化,也就相当于离线了。加入区间的每个点时对可持久化线段树建立一个新的版本。询问的时候线段树权值相减,然后线段树上二分板子即可。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
int n, m, A[MAXN], B[MAXN], tot = 0, Top = 0, root[MAXN];
struct Seg
{
int L, R, val;
} Sgt[MAXN << 5];
int BuildTree(int u, int L, int R)
{
u = ++ Top;
if(L == R)
{
Sgt[u].val = 0;
return u;
}
int mid = (L + R) >> 1;
Sgt[u].L = BuildTree(Sgt[u].L, L, mid);
Sgt[u].R = BuildTree(Sgt[u].R, mid + 1, R);
return u;
}
int Clone(int u)
{
Top ++;
Sgt[Top] = Sgt[u];
Sgt[Top].val ++;
return Top;
}
int Update(int u, int L, int R, int x)
{
u = Clone(u);
if(L == R) return u;
int mid = (L + R) >> 1;
if(x <= mid) Sgt[u].L = Update(Sgt[u].L, L, mid, x);
else Sgt[u].R = Update(Sgt[u].R, mid + 1, R, x);
return u;
}
int Query(int u, int v, int L, int R, int k)
{
if(L == R) return L;
int mid = (L + R) >> 1, xx = Sgt[Sgt[v].L].val - Sgt[Sgt[u].L].val, ans;
if(k <= xx) ans = Query(Sgt[u].L, Sgt[v].L, L, mid, k);
else ans = Query(Sgt[u].R, Sgt[v].R, mid + 1, R, k - xx);
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> A[i];
B[i] = A[i];
}
sort(B + 1, B + n + 1);
tot = unique(B + 1, B + n + 1) - B - 1;
root[0] = BuildTree(0, 1, tot);
for(int i = 1; i <= n; i ++) // 对可持久化线段树建立 n 个版本
{
int x = lower_bound(B + 1, B + tot + 1, A[i]) - B;
root[i] = Update(root[i - 1], 1, tot, x);
}
while(m --)
{
int L, R, k;
cin >> L >> R >> k;
cout << B[Query(root[L - 1], root[R], 1, tot, k)] << '\n';
}
return 0;
}
018. P5829 【模板】失配树
笑点解析,我联考模拟赛 T2 没看出来这是失配树。
根据 KMP 数组的链式传递性(这是熟知结论)。我们可以对文本串
我们跑 KMP 的时候把失配树上每个节点的父亲建立出来。然后跑一遍树上倍增预处理。
对于前缀串
- 如果
和 有祖先后代关系,那么其 'LCA' 就是它们真正 LCA 的父亲。这是为了保证一个串的 border 不为另一个串本身,这不符合 border 的定义。 - 如果
和 没有祖先后代关系,那么其 'LCA' 就是 和 真正的 LCA。因为不会出现互相包含的情况。
然后这道题我们就做完了。顺便说一下,联考那个所谓广义 border 的求法就是把祖先后代关系的那个特判删掉,变成了真正的 LCA,因为广义 border 规定了一个串的 border 可以是它自己。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000005;
string s;
int n, m, depth[MAXN], Parent[MAXN][25], lg[MAXN]; // 失配树
int LCA(int u, int v)
{
if(depth[u] < depth[v]) swap(u, v);
while(depth[u] > depth[v])
u = Parent[u][lg[depth[u] - depth[v]]];
// if(u == v) return u;
for(int i = lg[depth[u]]; i >= 0; i --)
{
if(Parent[u][i] == Parent[v][i]) continue;
u = Parent[u][i]; v = Parent[v][i];
}
return Parent[u][0];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> s >> m; n = s.length(); s = ' ' + s;
depth[1] = 1;
for(int i = 2; i <= n; i ++) lg[i] = lg[i >> 1] + 1;
for(int i = 2, j = 0; i <= n; i ++)
{
while(j > 0 && s[j + 1] != s[i]) j = Parent[j][0];
if(s[j + 1] == s[i]) j ++;
Parent[i][0] = j; depth[i] = depth[j] + 1;
}
for(int j = 1; j <= 24; j ++)
for(int i = 1; i <= n; i ++)
Parent[i][j] = Parent[Parent[i][j - 1]][j - 1];
while(m --)
{
int u, v;
cin >> u >> v;
cout << LCA(u, v) << '\n';
}
return 0;
}
019. P5838 [USACO19DEC] Milk Visits G
我们考虑将每一类点的 dfn 值统统按有序形态压入
那么现在问题变为:在
这其实我还写了
020. P11233 [CSP-S 2024] 染色(非常重要!!!)
考场降智,连
考虑
首先你可以和前面的和你相同的点染不同的颜色,但其实也不一定是不一样,反正你可以随便染,那么你的贡献起码是
第二种你前面从和你一样的点转移过来,那么我们可以证明你只有从离自己最近的相同点转移才是最优的。那么你的贡献就是
*020. [ARC058E] Iroha and Haiku *2473
过于神秘了,一眼丁真为我做不出来的题。
首先我们肯定知道如果正着做肯定是不好做的,一个很直接的原因就是贡献会算重复。然后就寄了。所以我们算有多少个序列是不好的。
然后我们考虑到它的
我们定义
什么情况下这个后缀和串是不好的呢?非常简单,有三种情况:
- 不存在一个后缀和等于
- 不存在一个后缀和等于
- 不存在一个后缀和等于
至于怎么计算后缀和串,这是简单的。我们考虑枚举 ((j << k) | (1 << (k - 1))) & ((1 << (X + Y + Z)) - 1)
。这是容易理解的。然后就是普通的计数,后面是简单的。
021. [ARC060D] Digit Sum *2261
非常神的题,但是我自己想到了。
根号分治。首先如果
如果
然后做完了,代码不长但根号分治一般都很神。
022. [ARC060E] Tak and Hotels *2154
倍增优化动态规划,我应该还是第一次理解这个东西。朴素的暴力就是暴力枚举,这里不做过多讲解。
考虑正解,设
然后就做完了。
023. [ARC060F] Best Representation *2804
Subtask 1:
考虑一个
注意到好串的定义就是之前题目里面的 Power Strings。然后就递推跑 KMP,应该是跑
Subtask 2:
首先特判:
- 如果字符串内的元素全部相等,那么必然要划分成
个串,个数就是 个。 - 如果字符串没有长度
的周期或者 ,那么可以不用划分,最小划分个数就是 ,个数也是 。
后面有一个非常强的结论,我不想证。就是如果存在周期长度
然后怎么计数呢?因为是划分成两段,所以我们很自然的想到枚举分界点,然后看前面一段是不是 Power Strings,然后看后面一段是不是 Power Strings,因为 KMP 跑出的 border 具有对称性,所以可以通过正反串两次 KMP 得出答案。最后判断是
024. CF1706E Qpwoeirut and Vertices 2300
连通性问题。考虑什么边才能对 “连通” 这一性质有贡献。非常简单,在无向图上只有树边有贡献。其他的返祖边都是无用的。这启发我们考虑最小生成树。
我们考虑把加入时间看做边权。对于询问
考虑怎么维护 LCA(i, i + 1)
的点权。
然后就是区间维护最大值问题,使用 ST 表解决即可。
然后这道题我们就做完了。
025. P9186 [USACO23OPEN] Milk Sum S
制杖题,细节这么多,害我写 40 min!
考虑先贪心。肯定小的排前面,大的排后面。修改就是把一个数抽走,再二分把这个数加进来。
然后你考虑把一个数抽走会发生什么。就是后面的贡献减去后面的后缀和,然后再减去自己的排名乘以自己的权值。
然后你考虑加入一个数会发生什么,反过来,就是贡献加上自己后面的后缀和,再加上自己的排名乘以自己的权值。注意这里有一个细节,我们统一把数插到第一个比自己大的位置,然后把比自己大的位置搞到后面。
注意判断两个位置之间的关系,这里还会有很多细节,不一一列举。
*026. CF1687C Sanae and Giant Robot 2500
题目大意非常清晰,这里不再复述。非常好的一道题。
然后我们进行暴力广度优先搜索,每次找到区间所在位置,暴力把 set 内在区间里的数的前缀和变为
027. CF2038K Grid Walk 2100
求
我们可以很容易的注意到同行权值一相同,同列权值二相同。注意到行列的格子必定会走一遍。所以我们尽量走权值一特别小和权值二特别小的格子。我们考虑先横着走,走到权值二为
然后有一个非常强的 Trick,这样找到两个位置
后面的直接动态规划解决,做完了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】