CF2164(Global Round 30)全解
试着搓一套新的难度玩。
Grok 居然能自己爬整场题并翻译了,好评。不过开长考翻译质量较差,建议开快速再让它重译一遍,顺便处理各种 markdown 锅。
本文中的题意纯手翻,没用 Grok,因为它复制 markdown 时公式没用美元符号。
更新日志:
- 25/11/2025:修复了 D 炸掉的代码块。
A. Sequence Game
- 知识点:无。
- 自评:练气(红)。
题意
给定长度为
求
数据范围:多测,
做法
显然可以通过不断操作与最值相邻的值来最大化最后一次操作的取值范围。
所以最终
:::success[代码]
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<fstream>
#include<algorithm>
#include<map>
#include<unordered_map>
#include<queue>
#include<deque>
#include<set>
#include<vector>
#define ll long long
#define lf double
#define ld long double
using namespace std;
ll T,n,a,mx,mn;
int main(){
cin>>T;
while(T--){
cin>>n>>a;
n--;
mx=a;
mn=a;
while(n--){
cin>>a;
mx=max(mx,a);
mn=min(mn,a);
}
cin>>a;
if(a<=mx&&a>=mn){
cout<<"YES\n";
}
else{
cout<<"NO\n";
}
}
return 0;
}
:::
B. Even Modulo Pair
- 知识点:无。
- 自评:元婴(绿)。
题意
给定长度为
如果存在,给出任意一对。
数据范围:多测,
做法
需要注意力。真注意不到。
若无解,
- 首先最多只能有一个偶数,因为偶数模偶数显然一定为偶数。
- 对于奇数,设数列里有一奇数
a_i ,则若要另外的奇数a_j 模a_i 不为偶数,则其必然要在[2ka_i,2(k+1)a_i](k\in \mathbb{Z}) 中。所以最多只能构造出\log V 个两两之间不符合要求的奇数。
发现
:::success[代码]
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<fstream>
#include<algorithm>
#include<map>
#include<unordered_map>
#include<queue>
#include<deque>
#include<set>
#include<vector>
#define ll long long
#define lf double
#define ld long double
using namespace std;
ll T,n,a[200010];
int main(){
ios::sync_with_stdio(0);
cin>>T;
while(T--){
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
n=min(n,40ll);
bool flg=0;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
if(a[j]%a[i]%2==0){
cout<<a[i]<<' '<<a[j]<<endl;
flg=1;
break;
}
}
if(flg){
break;
}
}
if(!flg){
cout<<-1<<endl;
}
}
return 0;
}
:::
C. Dungeon
- 知识点:
set。 - 自评:结丹(黄)。
题意
有
怪分两类:
一个怪只能打一次,求最多能打死多少怪。
数据范围:多测,
做法
显然要先打能爆装备的怪。而为了尽量使剑增强,每个这种怪都应该用尽量烂的剑去打。为了防止有怪不强化就打不死的情况打的时候按 multiset 维护,不会写也可以上树。
然后对于不爆装备的就枚举每个怪找最菜的剑打就行,顺序随意因为反正我每次用的是最菜的剑,打谁都一样。
:::success[代码]
关于 set 和 multiset 的基本操作:
set里的元素是自动排序的(是的这个很多打了两三年的选手都不知道),所以使用迭代器遍历set即可得到从小到大的顺序。set底层是红黑树所以其迭代器是双向迭代器,不能进行加减(无论与其他迭代器还是与整数),只能借助++或--操作一格一格移动;也不能比大小,所以使用迭代器遍历时需要写for(set<ll>::iterator i=s.begin();i!=s.end();i++),其中ll换成你所开set的类型,s换成你所开set的名称。set里有一个成员叫做lower_bound(),传入键类型值,返回迭代器,可以在set中进行二分查找。还有一个成员是find(),传入和返回同上,也是二分查找。两者的区别是前者找的是第一个大于等于所传入值的值,而后者只找恰好等于所传入值的值,当找不到符合条件的值时两者都会返回s.end()。set里有一个成员叫做erase(),传入迭代器,可以在set中删除。- 对于
priority_queue能代替的需求,最好不要用set,因为它常数巨大。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<fstream>
#include<algorithm>
#include<map>
#include<unordered_map>
#include<queue>
#include<deque>
#include<set>
#include<vector>
#define ll long long
#define lf double
#define ld long double
using namespace std;
struct node{
ll b,c;
};
bool operator <(node x,node y){
return x.b==y.b?x.c>y.c:x.b<y.b;
}
ll T,n,m,u,b[200010],p1,p0,ans;
multiset<ll> s;
node c[200010];
int main(){
ios::sync_with_stdio(0);
cin>>T;
while(T--){
cin>>n>>m;
s.clear();p0=p1=ans=0;
for(int i=0;i<n;i++){
cin>>u;
s.insert(u);
}
for(int i=0;i<m;i++){
cin>>b[i];
}
for(int i=0;i<m;i++){
cin>>u;
if(u){
c[p1].b=b[i];
c[p1++].c=u;
}
else{
b[p0++]=b[i];
}
}
sort(b,b+p0);
sort(c,c+p1);
for(int i=0;i<p1;i++){
set<ll>::iterator p=s.lower_bound(c[i].b);
if(p==s.end()){
break;
}
ans++;
ll tmp=(*p);
s.erase(p);
s.insert(max(tmp,c[i].c));
}
for(int i=0;i<p0;i++){
set<ll>::iterator p=s.lower_bound(b[i]);
if(p==s.end()){
break;
}
ans++;
s.erase(p);
}
cout<<ans<<endl;
}
return 0;
}
:::
D. Copy String
- 知识点:无。
- 自评:元婴(绿)。
题意
给定字符串
试问能否用不超过
数据范围:多测,
做法
容易想到一种直接匹配的方法:枚举
但是这个东西跑样例 #5 会输出
10 3
vzvylxxmsy
vvvvvllxxx
原因是
所以上述方法求出来的方案不是最优的。
于是考虑当当前
然后会发现我把
然后发现他是放
:::success[代码]
#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<fstream>
#include<algorithm>
#include<map>
#include<unordered_map>
#include<queue>
#include<deque>
#include<set>
#include<vector>
#include<cassert>
#define ll long long
#define lf double
#define ld long double
using namespace std;
ll T,n,k,l[1000010],r[1000010],lst[30];
string s,t;
bool work(ll now){
ll p=0;
bool flg=0;
memset(lst,-1,sizeof(lst));
for(int i=0;i<n;i++){
if(p<i){
flg=1;
break;
}
l[i]=p;
while(p<n&&t[p]==s[i]){
if(p-1-i==now)break;
p++;
}
r[i]=p-1;
}
if(flg){
return false;
}
string tmp=s;
for(int i=1;i<=now;i++){
for(int j=0;j<n;j++){
if(j+i<=r[j]){
tmp[j+i]=s[j];
}
}
}
if(tmp!=t){
return false;
}
cout<<now<<endl;
tmp=s;
for(int i=1;i<=now;i++){
for(int j=0;j<n;j++){
if(j+i<=r[j]){
tmp[j+i]=s[j];
}
}
cout<<tmp<<endl;
}
return true;
}
int main(){
ios::sync_with_stdio(0);
cin>>T;
while(T--){
cin>>n>>k>>s>>t;
bool flg=0;
for(int i=0;i<=k;i++){
if(work(i)){
flg=1;
break;
}
}
if(!flg){
cout<<-1<<endl;
}
}
return 0;
}
:::
E. Journey
- 知识点:Kruskal 重构树,欧拉回路。
- 自评:大乘(下位紫)至真仙(中位紫)。
题意
给定
- 选择一条与当前位置相连的边,走到边的另一端并标记该边,代价为该边边权。
- 选择一条以当前位置为起点的路径(点和边都可以重复走),走到路径的另一端,代价为路径上编号最大的边的边权。
求标记所有边并回到
数据范围:多测,
做法
官解评论区里有一个赛时自己发明出重构树的选手写的题解中算法过程比较清楚,对重构树不熟的同学可以参考。
当图有欧拉回路的时候显然可以只走一操作。
对于没有欧拉回路的情况,我们需要为度数为奇的点间进行连边使得每个点度数都变成偶数。新加入的边
而从
但二操作中允许绕路。所以除了简单路径上的必经边之外,也许可以通过绕路到一些下标更大但权值更小的边来减小代价。
这种情况下,由于每次添加的边都是下标尽量小的,所以重构树上
而从根往叶子走的过程中这个值显然是单调不升的,所以我们应该找尽可能深的、子树内含有两个或以上原图中奇度数点的重构树上的点并在这个时刻进行连边。
这样最终的复杂度就是
F. Chain Prefix Rank
- 前置:广义串并联图方法,平衡树。
- 自评:大罗(中位黑)。
鉴于笔者也没学过这种高科技,本题解简述广义串并联图的基础,所以不用担心看不懂。
题意
给定一棵
数据范围:
- 对于 F1:
\sum n\le 5000 。 - 对于 F2:
\sum n\le 5\times10^5 。
做法
看了无数篇 blog,所以没法列举出处了,因为我也不知道我在默写哪篇。如果你发现我大篇幅默了某篇你可以来找我。
这里讲官解做法。
我们引入广义串并联图的概念。
广义串并联图基础
定义
如果一个无向图中不存在四个点,使得这四个点间两两有路径相连,且路径间互不相交,则这个图被称为广义串并联图。树和仙人掌是常见的广义串并联图。
构造
- 一个两个点、一条边的无向图是一个广义串并联图。为了方便,我们为每个广义串并联图定一个源点和一个汇点。
- 将两个广义串并联图串联,即将其中一个的源点与另一个的汇点合并,得到的新图也是广义串并联图。该图源点为汇点被合并图的源点,汇点为源点被合并图的汇点。
- 将两个广义串并联图并联,即将两图的源点和汇点分别合并,得到的新图也是广义串并联图。合并后的源汇点即为新源汇点。
性质与判定
口诀:删一缩二并重边。
即广义串并联图可以经过若干次下面三种操作变成一个点:
- 删一度点:删掉一个度数为
1 的点。 - 缩二度点:若有边
(u,v),(v,w) ,则将其合并为一条边(u,w) 。 - 合并重边:将重边合并。
上述性质也可以用于判定。
上述性质中,由于这些操作通常容易合并信息,我们在操作的过程中可以在点上和边上维护信息以方便地进行统计。
更一般地,对于点数和边数相差较小的图,对其也进行上面的操作直至无法操作,可以将其剩余的点数和边数缩到可以暴力的范围,这种方法叫广义串并联图方法。设
本题
首先
所以为了找到排列我们可以考虑从根向下不断地在当前路径上插入点。我们建一张表示这个关系的图,为了确保每个点都能插入,我们在
这样得到的图视为无向图后显然是一个广义串并联图(因为插入的点可以“缩二度点”后“合并重边”来删掉),题目要求其拓扑序个数。很多题解里这里直接给的总式子,我简单展开一下。我们在边上维护信息,下设
- 缩二度点(左上图):两边的相对顺序固定所以方案直接相乘,点数加上删掉的点即可。有方程
f_{u,w}=f_{u,v}f_{v,w},sz_{u,w}=sz_{u,v}+sz{v,w}+1 。 - 合并重边(左下图):两边的相对顺序不固定,只需要确保内部顺序,所以方案相乘后应该加上安排点的位置的一项。点数直接相加。 有方程
f_{u,v}=f_{u,v}f'_{u,v}\text{C}_{sz{u,v}+sz'{u,v}}^{sz_{u,v}},sz_{u,v}=sz_{u,v}+sz'{u,v} 。 - 总式(右图):其实就是一次缩二度点一次合并重边,所以直接将合并重边式子中
f' 和sz' 全文替换成缩二度点的式子即可。有总方程f_{u,w}=f_{u,w}f_{u,v}f_{v,w}\text{C}_{sz{u,w}+sz{u,v}+sz{v,w}+1}^{sz_{u,w}},sz_{u,w}=sz_{u,w}+sz{u,v}+sz{v,w}+1 。
上述过程暴力实现是
G. Pointless Machine
- 知识点:Ad-hoc。
- 自评:金仙(上位紫)至太乙(下位黑)。
题意
交互题。给定
数据范围:多测,
做法
字母疑似和现有题解的做法一样,但我使用它们是因为 jiangly 讲这题的时候使用了。
首先
看上去询问次数的限制在提示我们按位考虑。考虑对于每个二进制位求出每个点向各编号点的连边数,将点分成这一位为
由于
本题的一个坑点就在于,此时很容易去想如何实现
jls 的做题经验告诉我们,形如
一算,这就是杭二学生出题的素质?
H. PalindromePalindrome
注意:本题解并不讲解如何证明最终复杂度是 不过反正开了
- 知识点:PAM,马拉车。
- 自评:大罗(中位黑)。
题意
定义一个字符串的幽默度为其中出现至少两次的最长的回文串的长度。
给定长度为
数据范围:
做法
官解翻译出锅催更暂无果,差评更了,虽然还是有锅。将部分正确的官解和各个资料拼着看得到了下述内容。
考虑回文子串两次出现位置的关系。显然可以简单地分为相交和不相交。
两次出现范围相交
好在新版官解的这部分改对了。
设两次出现的区间分别是
- 回文中心在
S[l,l'] 和S[r',r] 的回文中心(取边界)之间的所有回文子串。 -
-
第一条可以马拉车预处理,后两条可以上 PAM 找祖先,查询的时候直接上线段树即可。
两次出现范围不相交
对于这种情况,考虑以
我们在构建 PAM 时预处理出每个串的长度不超过其一半的最长回文后缀,每次跳这个后缀就可以只遍历我们需要的串了。查询还是上线段树即可。
总复杂度
下面的内容要感谢机房一位 Ag 爷愿意听我这个一等线上的小蒟蒻提问并和我探讨此题。
官解试图采用 runs 说明实际有效的串为
这根本不适用 runs,因为它就没有出现两次的周期。而且它应该有