Hello 2024 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17950558 ,转载请注明出处。
虽然已经退役了,但还是保持着打 CF 的习惯。
比赛开始时 15 分钟网页非常卡,导致 BC 两题吃了很多罚时和提交时间。最终排名 1166。
题目很有趣,推荐。(这不比隔壁 Goodbye 2023 好 114514 倍)
2024/1/19update:更新 F1、F2 题解。
E、G、H 题解请等待后续更新。
传送门
A.Wallet Exchange
题目大意
Alice 和 Bob 玩游戏,Alice 先手。
两人各有一个数字 。每个人需要依次进行下面两个操作:
-
交换两个人的数字或什么都不做。
-
将自己当前的数字减 。
如果自己的数字小于 则输。求最后谁会赢。
多组测试,。
思路
因为如果任意一方的数字不为 ,我们就可以将这个数字换给自己,所以游戏结束时两人的数字一定均为 。
判断两人初始数字和的奇偶性即可。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
using namespace std;
ll n,m;
void mian(){
scanf("%lld%lld",&n,&m);
if((n+m)&1)printf("Alice\n");
else printf("Bob\n");
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
B.Plus-Minus Split
题目大意
给定一个长度为 的序列 ,你需要将它划分成若干个区间。区间的权值为其中所有数的和的绝对值。
求所有区间权值和的最小值。
多组测试,。
思路
显然不划分就是最优的。直接计算答案即可。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
using namespace std;
ll n,m;
void mian(){
ll ans=0;
scanf("%lld",&n);
while(getchar()!='\n');
For(i,1,n){
if(getchar()=='+')++ans;
else --ans;
}
printf("%lld\n",abs(ans));
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
C.Grouping Increases
题目大意
给定一个长度为 的序列 。你有两个空序列 。
你需要依次操作每个序列 中的数。你可以选择将它加入 ,也可以选择将它加入 。
假设 的长度分别为 。定义权值 。
求权值 的最小值。
多组测试,。
思路
贪心地考虑每个数。记 的最后一个数为 ,初始时 为极大值。
定义一个数能加入序列,当且仅当这个数不大于序列的最后一个数。
考虑加入数 。
-
如果 能加入 也能加入 :将 加入 中较小值代表的集合。
-
如果 只能加入 或只能加入 :将 加入其能加入的集合。
-
如果 不能加入 也不能加入 :将 加入 中较小值代表的集合。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
ll n,m;
ll a[N],b[N];
void mian(){
ll ans=0;
ll t1=1e9,t2=1e9;
scanf("%lld",&n);
For(i,1,n){
scanf("%lld",&a[i]);
if(t1>=a[i]&&t2>=a[i]){
if(t1>t2)t2=a[i];
else t1=a[i];
}else if(t1>=a[i])t1=a[i];
else if(t2>=a[i])t2=a[i];
else if(t1<t2)t1=a[i],++ans;
else t2=a[i],++ans;
}
printf("%lld\n",ans);
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
D.01 Tree
题目大意
一棵树是好树当且仅当其满足以下条件:
-
所有非叶子节点都有两个儿子。
-
所有非叶子节点连向左右儿子的边中,一条边权值为 ,一条边权值为 。
定义树上两点的距离为它们的路径上所有边权的和。
将一棵好树的所有叶子节点按 dfs 序依次标号为 。
给定一个长度为 的序列 ,求是否存在一棵好树,满足 号节点到根节点的距离为 。存在输出 Yes
,否则输出 No
。
多组测试,。
思路
发现一个合法的序列一定有且仅有一个 ,且 在序列中时, 必定在序列中。
由于边权只有 ,所以如果我们需要在序列中插入一个数 ,那么一定只能在 的左边或右边。
直接判断给定的序列是否满足条件即可。
我们使用 dfs。
假设当前的数字为 ,区间为 。
用 vector 记录 出现的位置,二分找到所有在 内的 ,将 分隔为一些小区间,递归处理。
如果到达一个长度为 的区间且这个数不为 则输出 No
。
代码实现
注意特判 的个数。
如果使用笛卡尔树或者精细实现的 bfs 可以做到 。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;
ll n,m,flag;
ll a[N],b[N];
vector<ll>t[N];
void dfs(ll l,ll r,ll x){
if(l>r)return;
//二分
ll lx=lower_bound(t[x].begin(),t[x].end(),l)-t[x].begin();
ll rx=upper_bound(t[x].begin(),t[x].end(),r)-t[x].begin()-1;
if(rx-lx+1==0)return void(flag=1);//区间中的数不为x+1
//拆分小区间,递归
for(ll i=lx;i<=rx;++i){
dfs(l,t[x][i]-1,x+1);
l=t[x][i]+1;
}
dfs(l,r,x+1);
}
void mian(){
scanf("%lld",&n);
//预处理
flag=0;
For(i,0,n-1)t[i].clear();
For(i,1,n){
scanf("%lld",&a[i]);
t[a[i]].pb(i);
}
if(t[0].size()!=1){//特判
printf("No\n");
return;
}
For(i,0,n-1)t[i].pb((ll)1e9);//防止二分越界
dfs(1,n,0);
if(flag){
printf("No\n");
return;
}
printf("Yes\n");
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
E.Counting Prefixes
题目大意
思路
代码实现
F1.Wine Factory (Easy Version)
题目大意
与 F2 不同之处已标红。
现在有 座塔,第 座塔初始有 升水和一个能喝 升水的人。第 座塔与第 座塔之间有能通过 升水的管道。
接下来依次对 :
- 第 座塔的人喝掉尽可能多的水(不超过 )。
- 如果 ,最多 升水流向第 座塔。
定义答案为所有人喝掉的水量之和。
有 次修改,每次给定 ,表示修改 。你需要输出每次更新后的答案。
。
思路
仅适用 F1。
发现由于 ,所有没喝完的水都可以流到下一座塔中。
考虑计算没有被喝掉的水量。
定义 ,表示 流入 的水量,如果 ,这些水需要剩下的人喝掉。
那么整个后缀的贡献为 ,而答案则为 。
使用线段树维护即可,复杂度 。
代码实现
注意数组大小。
常数有点大。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=2e6+10;
using namespace std;
ll n,m,k,q;
ll a[N],b[N],c[N];
ll d[N];
//线段树
#define lson rt<<1
#define rson rt<<1|1
ll tr[N],lazy[N];
void pushdown(ll rt){
if(lazy[rt]){
lazy[lson]+=lazy[rt];
tr[lson]+=lazy[rt];
lazy[rson]+=lazy[rt];
tr[rson]+=lazy[rt];
lazy[rt]=0;
}
}
void change(ll rt,ll l,ll r,ll x,ll y,ll z){
if(x<=l&&r<=y){
tr[rt]+=z;
lazy[rt]+=z;
return;
}
pushdown(rt);
ll mid=(l+r)>>1;
if(x<=mid)change(lson,l,mid,x,y,z);
if(y>mid)change(rson,mid+1,r,x,y,z);
tr[rt]=max(tr[lson],tr[rson]);
}
void mian(){
ll ans=0,sum=0;
scanf("%lld%lld",&n,&q);
For(i,1,n)scanf("%lld",&a[i]);
For(i,1,n)scanf("%lld",&b[i]);
For(i,1,n-1)scanf("%lld",&c[i]);
Rep(i,n,1){
d[i]=a[i]-b[i];
sum+=d[i];//后缀和
ans+=a[i];
change(1,1,n,i,i,sum);
}
while(q--){
ll t,x,y,z;
scanf("%lld%lld%lld%lld",&t,&x,&y,&z);
ans+=x-a[t];
change(1,1,n,1,t,x-y-d[t]);
a[t]=x,b[t]=y,c[t]=z,d[t]=x-y;
printf("%lld\n",ans-max(0ll,tr[1]));//可能出现负数,需要对0取max
}
}
int main(){
int T=1;
while(T--)mian();
return 0;
}
F2.Wine Factory (Hard Version)
题目大意
与 F1 不同之处已标红。
现在有 座塔,第 座塔初始有 升水和一个能喝 升水的人。第 座塔与第 座塔之间有能通过 升水的管道。
接下来依次对 :
- 第 座塔的人喝掉尽可能多的水(不超过 )。
- 如果 ,最多 升水流向第 座塔。
定义答案为所有人喝掉的水量之和。
有 次修改,每次给定 ,表示修改 。你需要输出每次更新后的答案。
。
思路
发现 F1 的做法并不适用,也不好进行更多拓展。
我们仍然使用线段树,考虑将一段区间的塔看作一个塔,记录它能向后流多少水、还能喝掉多少水、管道还能装多少水、总共喝了多少水。
合并区间依次考虑过程即可,具体实现见代码。
代码实现
注意细节。
常数有点大。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=2e6+10;
using namespace std;
ll n,m,k,q;
ll a[N],b[N],c[N];
//线段树
#define lson rt<<1
#define rson rt<<1|1
struct node{
ll flow;//能向后流多少水
ll tmp;//还能喝掉多少水
ll pipe;//管道还能装多少水
ll res;//总共喝了多少水
}tr[N];
node operator+(node a,node b){
ll t=min(a.flow,b.tmp);//喝了多少水
node c;
ll t1=min(a.flow-t,b.pipe);//剩下流出的水
ll t2=min(b.tmp-t,a.pipe);//b还能喝多少水
c.flow=b.flow+t1;
c.tmp=a.tmp+t2;
c.pipe=min(a.pipe-t2,b.pipe-t1);//需要减去t2是因为这些水会在b处被喝掉,它们进了a的管道,但不进b的管道
c.res=a.res+b.res+t;
return c;
}
void change(ll rt,ll l,ll r,ll x){
if(l==r){
ll t=min(a[l],b[l]);//喝了多少水
ll t1=min(c[l],a[l]-t);//剩下流出的水
tr[rt]={t1,b[l]-t,c[l]-t1,t};
return;
}
ll mid=(l+r)>>1;
if(x<=mid)change(lson,l,mid,x);
else change(rson,mid+1,r,x);
tr[rt]=tr[lson]+tr[rson];
}
void mian(){
ll ans=0;
scanf("%lld%lld",&n,&q);
For(i,1,n)scanf("%lld",&a[i]);
For(i,1,n)scanf("%lld",&b[i]);
For(i,1,n-1)scanf("%lld",&c[i]);
For(i,1,n)change(1,1,n,i);
while(q--){
ll t,x,y,z;
scanf("%lld%lld%lld%lld",&t,&x,&y,&z);
a[t]=x,b[t]=y,c[t]=z;
change(1,1,n,t);
printf("%lld\n",tr[1].res);
}
}
int main(){
int T=1;
while(T--)mian();
return 0;
}
尾声
如果有什么问题,可以直接评论!
都看到这里了,不妨点个赞吧!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!