Hitachi Vantara Programming Contest 2024(AtCoder Beginner Contest 368)(A,B,C,D,E,F,G)

比赛链接

这场的题还不错,尤其是E题,FG偏简单。B是鸽巢原理,C要把几步操作看成一个整循环,然后加速递推,D没啥意思就是一个建图,E题写法很简单,但是思路需要把一列火车拆成两个时间点并分别处理,思路很精妙,F是博弈论SG函数,G是暴力模拟,set二分查找带个树状数组。


A - Cut

题面:

思路:

签到题,这题做法很多,总之说白了就是先打印后半段再打印前半段。

我假设这个数列是首尾相连循环的,那么就相当于要先打印第 − k -k k 个元素(假设数列从 0 0 0 开始编号,其实也就是第 n − k n-k nk 个),那么就从 − k -k k 开始,每次边加一边打印就可以了。

code:

#include <iostream>
#include <cstdio>
using namespace std; 
const int maxn=105;

int n,k;
int a[maxn];

int main(){
	cin>>n>>k;
	for(int i=0;i<n;i++)cin>>a[i];
	for(int i=0;i<n;i++)cout<<a[((i-k)%n+n)%n]<<" ";
	return 0;
}

B - Decrease 2 max elements

题面:

给你一个由 N N N 个正整数 A = ( A 1 , A 2 , … , A N ) A = (A_1, A_2, \dots ,A_N) A=(A1,A2,,AN) 组成的序列。高桥重复下面的操作,直到 A A A 只包含一个或更少的正整数元素:

  • A A A 按降序排序。然后将 A 1 A_1 A1 A 2 A_2 A2 减少 1 1 1

求他执行此操作的次数。

思路:

鸽巢原理。

如果 A 1 A_1 A1 这个最大的数足够大的话,那么它可以耗死后面所有的数,最后只留它一个。或者它不够大,最后这些数轮流抗刀最后只剩下一个 1 1 1 或者全 0 0 0

假设最大数为 m x mx mx,总和为 t o t tot tot A 1 A_1 A1 这个最大的数足够大指的就是它大于了剩余数之和即 m x > t o t − m x mx>tot-mx mx>totmx,也就是 2 × m x > t o t 2\times mx>tot 2×mx>tot m x mx mx 大于 t o t tot tot 的一半。这时会进行 t o t − m x tot-mx totmx 次运算。

如果不够大,那就大伙轮流抗刀,进行 t o t / 2 tot/2 tot/2 次,这里是整数除法,向下取整。

没卡long long,挺好。

code:

#include <iostream>
#include <cstdio>
using namespace std;

int n,mx,tot;

int main(){
	cin>>n;
	for(int i=1,t;i<=n;i++){
		cin>>t;
		mx=max(mx,t);
		tot+=t;
	}
	if(mx*2>tot)cout<<tot-mx;
	else cout<<tot/2;
	
	return 0;
}

C - Triple Attack

题意:

你正在玩一个游戏。

N N N 个敌人排成一排,最前面的 i i i 个敌人的健康值是 H i H _ i Hi

你将使用初始化为 0 0 0 的变量 T T T 重复以下操作,直到所有敌人的生命值都变为 0 0 0 或更少。

  • T T T 增加 1 1 1 。然后,攻击最前方生命值大于等于 1 1 1 的敌人。如果 T T T 3 3 3 的倍数,则敌人的生命值会减少 3 3 3 ;否则,生命值会减少 1 1 1

当所有敌人的生命值变为 0 0 0 或更少时,求 T T T 的值。

思路:

可以发现造成的伤害值是以 1 , 1 , 3 1,1,3 1,1,3 为循环的,只是结尾可能凑不齐整循环。那么我们可以暴力做结尾(反正就几步的事), 然后中间的循环直接算出循环了几次正循环,加速一下就好了。

思路很简单,写的时候可能要调一阵子。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;

int n,h[maxn];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>h[i];
	long long cur=0;
	for(int i=1;i<=n;i++){
		while(h[i]>0){
			if(h[i]>=5){
				cur+=h[i]/5*3;
				h[i]%=5;
			}
			else {
				cur++;
				h[i]-=(cur%3)?1:3;
			}
		}
	}
	cout<<cur<<endl;
	return 0;
}

D - Minimum Steiner Tree

题面:

给你一棵树,树上有 N N N 个顶点,编号为 1 1 1 N N N 。 第 i i i 条边连接顶点 A i A _ i Ai B i B_i Bi

考虑从这个图中删除一些边和顶点(可能为零)后可以得到一棵树。求这样一棵树中包含所有 K K K 指定顶点 V 1 , … , V K V _1,\ldots,V _ K V1,,VK 的顶点的最小数目。

思路:

假设树根必选,那么我们选某个节点就需要选上从根到这个节点之间的所有点和边,我们可以标记一下所有必选的顶点,然后把没有选上的点都删掉即可,答案就是剩余的顶点个数。

不过这个题没有指定根节点,也没说根一定必选,其实也好说,我们自己选一个指定点当根就行了。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;

int n,k;

int head[maxn],ent;
struct edge{
	int v,nxt;
}e[maxn<<1];
void add(int u,int v){
	e[++ent]={v,head[u]};
	head[u]=ent;
}

int fa[maxn],cnt;
bool vis[maxn];
void dfs(int u){
	for(int i=head[u],v;i;i=e[i].nxt){
		v=e[i].v;
		if(v==fa[u])continue;
		fa[v]=u;
		dfs(v);
	}
}

int main(){
//	cin.tie(0)->sync_with_stdio(false);
	cin>>n>>k;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	
	int u;
	cin>>u;
	dfs(u);
	vis[u]=true;
	cnt++;
	for(int i=2;i<=k;i++){
		cin>>u;
		while(!vis[u]){
			vis[u]=true;
			cnt++;
			u=fa[u];
		}
	}
	cout<<cnt<<endl;
	return 0;
}

E - Train Delay

题面:

在 Atcoder 国家,有 N N N 座城市,编号为 1 1 1 N N N ,以及 M M M 列火车,编号为 1 1 1 M M M i i i 次列车于 S i S_i Si 时从城市 A i A_i Ai 出发,于 T i T_i Ti 时到达城市 B i B_i Bi

给定一个正整数 X 1 X_1 X1 ,求一个非负整数 X 2 , … , X M X_2,\ldots,X_M X2,,XM 的最小值 X 2 + … + X M X_2+\ldots+X_M X2++XM ,以满足下面的条件。

  • 条件对于所有满足 1 ≤ i , j ≤ M 1 \leq i,j \leq M 1i,jM 的一对 ( i , j ) (i,j) (i,j) ,若 B i = A j B_i=A_j Bi=Aj T i ≤ S j T_i \leq S_j TiSj ,则 T i + X i ≤ S j + X j T_i+X_i \leq S_j+X_j Ti+XiSj+Xj
    • 换句话说,对于任何一对原本可以换乘的列车,即使将每列列车 i i i 的出发和到达时间延迟 X i X_i Xi ,仍然可以换乘。

可以证明,这种将 X 2 , … , X M X_2,\ldots,X_M X2,,XM 设为 X 2 + … + X M X_2+\ldots+X_M X2++XM 的最小值的方法是唯一的。

思路:

苦思冥想一上午,最后还是看了jiangly大佬录播的代码才会的,人家看一眼题目立马就会了,提键盘就写,太强了。

这个题的题意就比较晦涩,解释一下大概就是: B i = A j B_i=A_j Bi=Aj 指的就是第 i i i 辆火车的终点站等同于第 j j j 辆火车的起始站,并且 T i ≤ S j T_i \leq S_j TiSj i i i 辆火车的到达时间早于第 j j j 辆火车的发车时间(意味着从第 i i i 辆火车下车的乘客可以在这换乘第 j j j 辆火车)。现在要求 T i + X i ≤ S j + X j T_i+X_i \leq S_j+X_j Ti+XiSj+Xj i i i 辆火车延时 X i X_i Xi 后,让第 j j j 辆火车延时 X j X_j Xj,使得仍然可以满足第 i i i 辆火车的到达时间早于第 j j j 辆火车的发车时间(意味着仍然可以换乘)。

在一列火车 j j j 的起始站点上,需要满足它发车要慢于 以它为终点站,并且原本到站就早于它发车 的所有火车 i i i 的条件(其实只要晚于最晚到站的那列火车就可以了)。然后这列火车 j j j 就会在终点站产生一个新的到站时间。

我们发现一列火车的发车要看发车站的目前最晚到站时间,到站后再刷新一个到站时间。发车和到站是相对分离的关系,因此我们可以把一列火车的两个时间信息拆成发车时间和到站时间,然后排序,再设置一个当前的最晚到站时间。
当我们拿到一个发车时间时,前面的到站信息就都处理完了,我们看到的当前站点更新的就是目前最晚的到站时间了,可以直接算出这列列车的 X X X 值(也就是最小的延迟发车时间)。当我们拿到一个到站时间,这时候这列火车的 X X X 值一定被算出来了(因为到站时间一定不早于发车时间,相等时我们可以在排序时将发车时间排在前面,这样一对时间一定是发车在前),我们就可以直接算出这列火车的到站时间了。跑一遍就算出了所有 X X X 值。

不得不说真的神乎其技,瞬间就想到了这种相互耦合的关系,并得到了解法。排序时的处理更是天衣无缝。

code:

#include <iostream>
#include <cstdio>
#include <vector>
#include <array>
#include <algorithm>
using namespace std; 
const int maxn=2e5+5;

int n,m,x[maxn],tm[maxn];
int A[maxn],B[maxn],S[maxn],T[maxn];

int main(){
	cin>>n>>m>>x[1];
	for(int i=1;i<=m;i++)cin>>A[i]>>B[i]>>S[i]>>T[i];
	
	vector<array<int,3> > a;
	for(int i=1;i<=m;i++){
		a.push_back({S[i],1,i});
		a.push_back({T[i],0,i});
	}
	sort(a.begin(),a.end());
	for(auto [Time,o,i]:a){
		if(o==1){
			x[i]=max(x[i],tm[A[i]]-S[i]);
		}
		else {
			tm[B[i]]=max(tm[B[i]],T[i]+x[i]);
		}
	}
	for(int i=2;i<=m;i++)cout<<x[i]<<" ";
	return 0;
}

F - Dividing Game

题面:

问题陈述

给你一个由 N N N 个正整数 A = ( A 1 , A 2 , … , A N ) A = (A_1, A_2, \dots ,A_N) A=(A1,A2,,AN) 组成的序列,其中每个元素至少是 2 2 2AnnaBruno 用这些整数玩一个游戏。他们轮流执行以下操作,安娜先执行。

  • 自由选择一个整数 i   ( 1 ≤ i ≤ N ) i \ (1 \leq i \leq N) i (1iN) 。然后,自由选择 A i A_i Ai 的一个不是 A i A_i Ai 本身的正整数因子 x x x ,并用 x x x 代替 A i A_i Ai

不能进行操作的一方输,另一方赢。假定两位棋手都以最佳方式下棋,谁会获胜?

思路:

没什么特别的模型可套,还能用SG函数的题,基本就是SG函数题,用不了的可能是打表找规律。

对一个数可以用SG函数来求解。 1 1 1 是终止状态,因此 s g [ 1 ] = 0 sg[1]=0 sg[1]=0,用 s e t set set 存储每个数的所有因子的SG函数值。然后每枚举到一个数都算一下它的 m e x mex mex 值作为它本身的SG函数值,再枚举一下它的倍数,把它的SG值给到它的所有倍数,以此类推。

每次找完一个数可以清空它的 s e t set set 节省空间,不清问题应该也不大。平均一个数的因子个数是 log ⁡ \log log 个,所以一共也就存 n log ⁡ n n\log n nlogn 个数。

枚举每一个数的倍数的枚举次数是调和级数,即 n + n 2 + n 3 + ⋯ + + n n = n ∗ ( 1 1 + 1 2 + ⋯ + 1 n ) ≈ n log ⁡ n n+\dfrac{n}2+\dfrac{n}3+\dots++\dfrac{n}n=n*(\dfrac{1}1+\dfrac{1}2+\dots+\dfrac{1}n)\approx n\log n n+2n+3n+++nn=n(11+21++n1)nlogn 次。不会超时。

一个数的求出来的。对于多个数同时进行的 n i m nim nim 游戏,这一整个的局面的SG函数值就是将多个游戏的SG函数值全异或起来。

code:

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int maxn=1e5+5;

int sg[maxn];
set<int> SG[maxn];
void shai(){
	sg[1]=0;
	for(int i=2;i<=1e5;i++)SG[i].insert(sg[1]);
	for(int i=2;i<=1e5;i++){
		for(int j=0;;j++)
			if(SG[i].find(j)==SG[i].end()){
				sg[i]=j;
				SG[i].clear();
				break;
			}
		for(int j=2;j*i<=1e5;j++){
			SG[j*i].insert(sg[i]);
		}
	}
}

int n;

int main(){
	shai();
	cin>>n;
	int ans=0;
	for(int i=1,t;i<=n;i++){
		cin>>t;
		ans^=sg[t];
	}
	puts((ans)?"Anna":"Bruno");
	return 0;
}

不知道为什么只筛素数倍数也是对的,我不会证,有大佬会证可以在评论区赐教。

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int maxn=1e5+5;

bool vis[maxn*20];
int prime[maxn],sg[maxn],cnt;
set<int> SG[maxn];
void shai(){
	sg[1]=0;
	for(int i=2;i<=1e5;i++)SG[i].insert(sg[1]);
	for(int i=2;i<=1e5;i++){
		for(int j=0;;j++)
			if(SG[i].find(j)==SG[i].end()){
				sg[i]=j;
				SG[i].clear();
				break;
			}
		if(!vis[i]){
			vis[i]=true;
			for(int j=2;j*i<=1e5;j++){
				SG[j*i].insert(sg[i]);
			}
		}
	}
}

int n;

int main(){
	shai();
	cin>>n;
	int ans=0;
	for(int i=1,t;i<=n;i++){
		cin>>t;
		ans^=sg[t];
	}
	puts((ans)?"Anna":"Bruno");
	return 0;
}

G - Add and Multiply Queries

题面:

给你长度为 N N N 的正整数序列 A A A B B B 。请依次处理以下列形式给出的 Q Q Q 个查询。每个查询属于以下三种类型之一。

  • 类型 1 1 1 :以 1 i x 的形式给出。将 A i A_i Ai 替换为 x x x
  • 类型 2 2 2 :以 2 i x 的形式给出。用 x x x 代替 B i B_i Bi
  • 输入 3 3 3 :以 3 l r 的形式给出。求解以下问题并打印答案。
    • 首先,设置 v = 0 v = 0 v=0 。依次将 i = l , l + 1 , . . . , r i = l, l+1, ..., r i=l,l+1,...,r 中的 v v v 替换为 v + A i v + A_i v+Ai v × B i v \times B_i v×Bi 。最后求出 v v v 的最大可能值。

保证给定类型 3 3 3 查询的答案最多为 1 0 18 10^{18} 1018

思路:

只看形式的话其实很像线段树,但是常规做法没法完成操作 3 3 3

可以发现一个性质,就是当 B i = 1 B_i=1 Bi=1 时,我们乘上它是没有意义的,还不如直接加 A i A_i Ai。但是如果要乘大于等于 2 2 2 的数的话,乘法的增长速度很快。因为保证给定类型 3 3 3 查询的答案最多为 1 0 18 10^{18} 1018,那么在查询区间内乘法最多不可能超过 60 60 60 次,查询大区间里必定大段都是 B i = 1 B_i=1 Bi=1

那么我们可以暴力地计算每个 B i > 1 B_i>1 Bi>1 的地方(反正也就几十次),中间每一段 B i = 1 B_i=1 Bi=1 的段可以用树状数组或者线段树维护出 A i A_i Ai 的区间和来快速累加即可。

code:

#include <iostream>
#include <cstdio>
#include <set>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;

ll n,q,a[maxn],b[maxn];
set<ll> S;

struct BIT{
	int n;
	vector<ll> tr;
	
	void build(int _n){
		n=_n;
		tr.resize(n+1);
	}
	inline int lowbit(int x){return x&-x;}
	void add(ll x,int id){
		for(int i=id;i<=n;i+=lowbit(i))
			tr[i]+=x;
	}
	ll query(int id){
		ll ans=0;
		for(int i=id;i;i-=lowbit(i))
			ans+=tr[i];
		return ans;
	}
	inline ll query(int l,int r){
		if(l>r)return 0;
		else return query(r)-query(l-1);
	}
}tr;


int main(){
	cin.tie(0)->sync_with_stdio(false);
	cin>>n;tr.build(n);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		tr.add(a[i],i);
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
		if(b[i]>1)S.insert(i);
	}
	cin>>q;
	for(int op,l,r;q;q--){
		cin>>op>>l>>r;
		if(op==1){
			tr.add(-tr.query(l,l)+r,l);
			a[l]=r;
		}
		else if(op==2){
			if(b[l]==1 && r>1)S.insert(l);
			else if(b[l]>1 && r==1)S.erase(l);
			b[l]=r;
		}
		else {
			int id;
			ll ans=0;
			while(true){
				auto it=S.lower_bound(l);
				if(it==S.end() || *it>r){
					ans+=tr.query(l,r);
					break;
				}
				id=*it;
//				cout<<id<<endl;
				ans+=tr.query(l,id-1);
				ans=max(ans+a[id],ans*b[id]);
				l=id+1;
			}
			cout<<ans<<endl;
		}
	}
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值