Hello 2024 题解

本文网址:https://www.cnblogs.com/zsc985246/p/17950558 ,转载请注明出处。

虽然已经退役了,但还是保持着打 CF 的习惯。

比赛开始时 15 分钟网页非常卡,导致 BC 两题吃了很多罚时和提交时间。最终排名 1166。

题目很有趣,推荐。(这不比隔壁 Goodbye 2023 好 114514 倍)

2024/1/19update:更新 F1、F2 题解。

E、G、H 题解请等待后续更新

传送门

Hello 2024

A.Wallet Exchange

题目大意

Alice 和 Bob 玩游戏,Alice 先手。

两人各有一个数字 a,b。每个人需要依次进行下面两个操作:

  1. 交换两个人的数字或什么都不做。

  2. 将自己当前的数字减 1

如果自己的数字小于 0 则输。求最后谁会赢。

多组测试,1a,b109,T1000

思路

因为如果任意一方的数字不为 0,我们就可以将这个数字换给自己,所以游戏结束时两人的数字一定均为 0

判断两人初始数字和的奇偶性即可。

代码实现

#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

题目大意

给定一个长度为 n 的序列 a,你需要将它划分成若干个区间。区间的权值为其中所有数的和的绝对值

求所有区间权值和的最小值。

多组测试,1n5000,T1000

思路

显然不划分就是最优的。直接计算答案即可。

代码实现

#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

题目大意

给定一个长度为 n 的序列 a。你有两个空序列 s,t

你需要依次操作每个序列 a 中的数。你可以选择将它加入 s,也可以选择将它加入 t

假设 s,t 的长度分别为 p,q。定义权值 val=i=1p1[si<si+1]+i=1q1[ti<ti+1]

求权值 val 的最小值。

多组测试,1n2×105,1ain,T104,n2×105

思路

贪心地考虑每个数。记 s,t 的最后一个数为 x,y,初始时 x,y 为极大值。

定义一个数能加入序列,当且仅当这个数不大于序列的最后一个数

考虑加入数 z

  1. 如果 z 能加入 s 也能加入 t:将 z 加入 x,y 中较小值代表的集合。

  2. 如果 z 只能加入 s 或只能加入 t:将 z 加入其能加入的集合。

  3. 如果 z 不能加入 s也不能加入 t:将 z 加入 x,y 中较小值代表的集合。

代码实现

#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

题目大意

一棵树是好树当且仅当其满足以下条件:

  1. 所有非叶子节点都有两个儿子。

  2. 所有非叶子节点连向左右儿子的边中,一条边权值为 0,一条边权值为 1

定义树上两点的距离为它们的路径上所有边权的和。

将一棵好树的所有叶子节点按 dfs 序依次标号为 1n

给定一个长度为 n 的序列 a,求是否存在一棵好树,满足 i 号节点到根节点的距离为 ai。存在输出 Yes,否则输出 No

多组测试,2n2×105,0ain1,T104,n2×105

思路

发现一个合法的序列一定有且仅有一个 0,且 x(x>0) 在序列中时,x1 必定在序列中。

由于边权只有 0,1,所以如果我们需要在序列中插入一个数 x,那么一定只能在 x1 的左边或右边

直接判断给定的序列是否满足条件即可。

我们使用 dfs。

假设当前的数字为 x,区间为 [l,r]

用 vector 记录 x+1 出现的位置,二分找到所有在 [l,r] 内的 x+1,将 [l,r] 分隔为一些小区间,递归处理。

如果到达一个长度为 1 的区间且这个数不为 x+1 则输出 No

代码实现

注意特判 0 的个数。

如果使用笛卡尔树或者精细实现的 bfs 可以做到 O(n)

#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 不同之处已标红。

现在有 n 座塔,第 i 座塔初始有 ai 升水和一个能喝 bi 升水的人。第 i 座塔与第 i+1 座塔之间有能通过 ci 升水的管道。

接下来依次对 i[1,n]

  1. i 座塔的人喝掉尽可能多的水(不超过 bi)。
  2. 如果 in,最多 ci 升水流向第 i+1 座塔。

定义答案为所有人喝掉的水量之和

q 次修改,每次给定 t,x,y,z,表示修改 at=x,bt=y,ct=z。你需要输出每次更新后的答案。

2n5×105,1q5×105,0ai,bi,x,y109,ci=z=1018,1tn

思路

仅适用 F1。

发现由于 ci=1018,所有没喝完的水都可以流到下一座塔中。

考虑计算没有被喝掉的水量

定义 di=aibi,表示 i 流入 i+1 的水量,如果 di>0,这些水需要剩下的人喝掉。

那么整个后缀的贡献为 resi=j=indj,而答案则为 i=1naimaxi=1n(ansi)

使用线段树维护即可,复杂度 O((n+q)logn)

代码实现

注意数组大小。

常数有点大。

#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 不同之处已标红。

现在有 n 座塔,第 i 座塔初始有 ai 升水和一个能喝 bi 升水的人。第 i 座塔与第 i+1 座塔之间有能通过 ci 升水的管道。

接下来依次对 i[1,n]

  1. i 座塔的人喝掉尽可能多的水(不超过 bi)。
  2. 如果 in,最多 ci 升水流向第 i+1 座塔。

定义答案为所有人喝掉的水量之和

q 次修改,每次给定 t,x,y,z,表示修改 at=x,bt=y,ct=z。你需要输出每次更新后的答案。

2n5×105,1q5×105,0ai,bi,x,y109,0ci,z1018,1tn

思路

发现 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;
}

尾声

如果有什么问题,可以直接评论!

都看到这里了,不妨点个赞吧!

posted @   zsc985246  阅读(302)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示