2025年最后一搏—— Educational Codeforces Round 186 (Rated for Div. 2) 题解

一、前言

这场比赛和新年赛时间距离很近。本来我不应该参与这场比赛(周中),但是我直到比赛前 5 5 5 分钟才写完学校作业,抱着最后一搏的心态,我参与了这场比赛......

二、正文

第 A 题 New Year String

观察发现,只有存在 2025 2025 2025 且不存在 2026 2026 2026 时,答案才是正数,否则就是零。

这时,我们将一个 2025 2025 2025 的 5 5 5 改为 6 6 6,此时新串合法,答案为 1 1 1。

对于判断,使用字符串的 s.substr(i,4) 来获得长度为 4 4 4 的子串。

代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int t; cin>>t;
	while (t--){
		int n; cin>>n;
		string s; cin>>s;
		int cnt1=0,cnt2=0;
		for (int i=0; i<s.size()-3; i++){
			if (s.substr(i,4)=="2025") cnt1++;
			else if (s.substr(i,4)=="2026") cnt2++;
		}
		if (cnt1==0||cnt2!=0) cout<<"0\n";
		else cout<<"1\n";
	}
} 

第 B 题 New Year Cake

这道题也非常简单。

考虑枚举一下最小的那层的黑白,然后不停地暴力迭代,每次先判断当前颜色巧克力是否足够,如果足够,翻转颜色,所需大小翻倍,答案加 1 1 1。

取两次答案的最大值即可。

代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int t; cin>>t;
	while (t--){
		int n,m; cin>>n>>m;
		int cnt0=0,cnt1=0;
		int x=n,y=m,now=1;
		while (true){
			if (x<now) break; 
			cnt0++; x-=now; now*=2;
			if (y<now) break;
			cnt0++; y-=now; now*=2;
		}
		x=m,y=n,now=1;
		while (true){
			if (x<now) break; 
			cnt1++; x-=now; now*=2;
			if (y<now) break;
			cnt1++; y-=now; now*=2;
		}
		cout<<max({cnt0,cnt1})<<"\n";
	}
} 

第 C 题 Production of Snowmen

假设我们选取 i , j , k i,j,k i,j,k 为三个数组的起点。

那么我们发现只要 a , b a,b a,b 和 b , c b,c b,c 拧完后相等即可。

我们会发现判断是否相等时,反向拧回一定次数也不影响判断。

所以 a a a 不动和 b b b 拧 j − i j-i j−i 次相等, b b b 不动和 c c c 拧 k − j k-j k−j 次相等。

那么我们考虑有多少 j − i j-i j−i 和 k − j k-j k−j 满足条件。那么假设分别有 x , y x,y x,y 种选法,那么 i i i 还有 n n n 种选法,那么答案为 x × y × n x\times y\times n x×y×n。

发现 n ≤ 5000 n\le 5000 n≤5000,暴力枚举判断即可,具体见代码。

代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[5010],b[5010],c[5010];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int t; cin>>t;
	while (t--){
		int n; cin>>n;
		for (int i=1; i<=n; i++) cin>>a[i];
		for (int i=1; i<=n; i++) cin>>b[i];
		for (int i=1; i<=n; i++) cin>>c[i];
		int cnt1=0,cnt2=0;
		for (int i=1; i<=n; i++){
			bool ok=1;
			for (int j=1; j<=n; j++){
				ok&=(b[(i+j-2)%n+1]>a[j]); 
			}
			if (ok) cnt1++;
			ok=1;
			for (int j=1; j<=n; j++){
				ok&=(c[(i+j-2)%n+1]>b[j]);
			}
			if (ok) cnt2++; 
		}
		cout<<n*cnt1*cnt2<<"\n";
	}
} 

第 D 题 Christmas Tree Decoration

n ≤ 50 n\le 50 n≤50 的限制条件纯属吓人使用,其实人家是 O ( n ) O(n) O(n) 解决的。

那么我们首先发现对于 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 里面的最大值 m x mx mx,那么这个过程至少持续了 m x − 1 mx-1 mx−1 整轮,还会额外进行几次。

那么剩下的 a 0 a_0 a0 怎么办呢?我们发现 a 0 a_0 a0 是帮助那些不能选的人救命的礼物。如果结束时救命药够用,那么我们一定能够合法地取完这些礼物。

那么我们首先计算 ∑ i = 1 n max ⁡ ( 0 , m x − 1 − a i ) \sum_{i=1}^{n}\max(0,mx-1-a_i) ∑i=1nmax(0,mx−1−ai) 作为最后一轮前救命药的使用次数。

假设剩余了 x x x 个救命药,那么最后一轮的限制就是在最后一个 m x mx mx 取完前,不能超过 x x x 个数不是 m x mx mx。

考虑最后一个 m x mx mx 前有 i i i 个不是 m x mx mx,假设 m x mx mx 有 u n u unu unu 个,其余有 u s e use use 个,那么我们的方案数为:

C u s e i × u n u × ( u n u − 1 + i ) ! × ( u s e − 1 ) ! C_{use}^{i}\times unu\times (unu-1+i)!\times(use-1)! Cusei×unu×(unu−1+i)!×(use−1)!

求和即可,对于阶乘和组合数,预处理即可。

代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define M 998244353
int a[60],C[60][60],inv[60];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int t; cin>>t; inv[0]=1;
	for (int i=1; i<=50; i++) inv[i]=inv[i-1]*i%M;
	C[0][0]=1;
	for (int i=1; i<=50; i++){
		C[i][0]=C[i][i]=1;
		for (int j=1; j<i; j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%M;
	}
	while (t--){
		int n; cin>>n;
		for (int i=0; i<=n; i++) cin>>a[i];
		int mx=0,cnt=0,use=0,unu=0;
		for (int i=1; i<=n; i++){
			mx=max(mx,a[i]);
		}
		for (int i=1; i<=n; i++){
			cnt+=max(0ll,mx-a[i]-1);
			if (mx==a[i]) unu++;
			else use++;
		}
		a[0]-=cnt; int ans=0;
		for (int i=0; i<=min(use,a[0]); i++){
			ans+=C[use][i]*unu%M*inv[unu-1+i]%M*inv[use-i]%M;
		}
		cout<<ans%M<<"\n";
	}
} 

第 E 题 New Year's Gifts

纯属吓人。

考虑将手中的钱减少 ∑ y \sum y ∑y,那么问题转化为用 x x x 的盒子和 z − y z-y z−y 元钱可以使得一个人开心,那么最多让多少个人开心。

那么我们先让人的 x x x 和 盒子的美丽度排序,然后双指针动态插入这个盒子能够打动的所有人,然后贪心地删除掉 z − y z-y z−y 最大的那个人。

最后考虑每次访问 z − y z-y z−y 最小的那个人,如果钱够用,那么打动那个人并将他删除。我们可以使用 set 来维护这个过程。

代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define M 998244353
struct node{int x,y,z;}a[200010];
bool cmp(node a,node b){
	return a.x<b.x;
}
int b[200010];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int t; cin>>t;
	while (t--){
		int n,m,k; cin>>n>>m>>k;
		multiset <int> st;
		for (int i=1; i<=m; i++) cin>>b[i];
		sort(b+1,b+m+1); 
		for (int i=1; i<=n; i++){
			cin>>a[i].x>>a[i].y>>a[i].z;
			k-=a[i].y;
		}
		sort(a+1,a+n+1,cmp); int id=1;
		for (int i=1; i<=m; i++){
			while (id<=n&&a[id].x<=b[i]) st.insert(a[id].z-a[id].y),id++;
			if (st.size()){
				st.erase(st.find(*(st.rbegin())));
			}
		}
		while (id<=n) st.insert(a[id].z-a[id].y),id++;
		//cout<<k<<" "<<st.size()<<" ";
		while (st.size()&&k>=(*(st.begin()))){
			k-=(*(st.begin()));
			st.erase(st.begin());
		}
		cout<<n-st.size()<<"\n";
	}
} 

第 F1F2 题 Christmas Reindeer

这两道题目其实并不是非常难,我们直接考虑 F2。

第一步,我们观察发现,如果将所有的鹿按照力量从大到小排序并按照这个顺序选择鹿,那么鹿的力量是单调递减的。

那么我们考虑是否选择一只最终贡献是 2 i 2^i 2i 的鹿,从大到小考虑,为了使总力量大于 x x x,情况分两种:

如果 x x x 有 2 i 2^i 2i 这一位

那么我们只能选择这一位,假设前面有 a a a 只鹿,那么初始力量为 2 a + i 2^{a+i} 2a+i 的鹿将减少一只。

如果 x x x 没有这一位

那么我们如果能够选择 2 i 2^i 2i 这一位,那么一定是总力量 > x >x >x 的合法方案。

考虑每种鹿还剩 n − x n-x n−x 只,那么这一种鹿的贡献就是 ∑ i = 0 n − x C c n t i \sum_{i=0}^{n-x}C_{cnt}^{i} ∑i=0n−xCcnti,其中 c n t cnt cnt 是鹿的总数量。

那么这个东西可以预处理一下(容斥法),其中 x x x 一定是不超过 60 60 60 的(因为只有这么多位)

我们还可以不选这一位,那么直接进入下一位考虑。

那么如何动态维护方案,每一次相当于修改一位(赋值)并且更新乘积。那么我们直接求逆元是会 TLE 的。我给出的方案是线段树维护(因为线段树可以直接赋值)。

注意恰好为 x x x 的情况。

代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ls (id<<1)
#define rs (id<<1|1)
#define mid (l+r>>1)
#define M 998244353
#define K 1000000000
int cnt[70],res,tmp[70],cnt2[70];
int C[600010][65];
int Pow(int a,int n){
	int ans=1;
	while (n){
		if (n&1) ans=ans*a%M;
		a=a*a%M; n>>=1;
	}
	return ans;
}
int num[260];
void update(int id,int l,int r,int qid,int val){
	if (l==r){num[id]=val; return ;}
	if (qid<=mid) update(ls,l,mid,qid,val);
	else update(rs,mid+1,r,qid,val);
	num[id]=num[ls]*num[rs]%M;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int n,m; cin>>n>>m;
	C[0][0]=1;
	for (int i=1; i<=n+m; i++){
		C[i][0]=1; 
		if (i<=61) C[i][i]=1;
		for (int j=1; j<min(i,62ll); j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%M;
	}
	for (int i=1; i<=n; i++){
		int x; cin>>x; cnt[x]++;
	}
	for (int i=0; i<=n+m; i++){
		int tmp=Pow(2,i);
		for (int j=0; j<=min(j,61ll); j++){
			C[i][j]+=K*tmp; 
			tmp=(tmp+M-C[i][j]%K)%M;
		}
	}
	for (int i=1; i<=m; i++){
		int op,x; cin>>op;
		if (op==1) cin>>x,cnt[x]++;
		if (op==2) cin>>x,cnt[x]--;
		if (op==3){
			int x; cin>>x;
			for (int i=0; i<=60; i++){
				update(1,0,60,i,C[cnt[i]][0]/K); cnt2[i]=cnt[i];
			}
			int now=0,ans=0,err=1;
			for (int i=60; i>=0; i--){
				if (x&(1ll<<i)){
					if (!cnt2[i+now]) {err=0; break;}
					cnt2[i+now]--;
					update(1,0,60,i+now,C[cnt[i+now]][cnt[i+now]-cnt2[i+now]]/K);
					now++;
				}
				else{
					if (cnt2[i+now]){
						cnt2[i+now]--;
						update(1,0,60,i+now,C[cnt[i+now]][cnt[i+now]-cnt2[i+now]]/K);
						ans=(ans+num[1])%M;
						cnt2[i+now]++;
						update(1,0,60,i+now,C[cnt[i+now]][cnt[i+now]-cnt2[i+now]]/K);
					}
					update(1,0,60,i+now,C[cnt[i+now]][cnt[i+now]-cnt2[i+now]]%K);
				}
			}
			cout<<(ans+err*num[1])%M<<"\n";
		}
	}
} 

二、后记

上金成功了,真是以外之喜啊!

至此,2025 年双金目标在最后几天里实现了。

相关推荐
微光闪现2 小时前
国际航班动态提醒与延误预测优选平台指南
大数据·人工智能·算法
leoufung2 小时前
LeetCode 120. Triangle:从 0 分到 100 分的思考过程(含二维 DP 与空间优化)
linux·算法·leetcode
gihigo19982 小时前
基于反步法的路径追踪控制
算法
Jim-2ha02 小时前
【JavaScript】常见排序算法实现
javascript·算法·排序算法
王老师青少年编程3 小时前
2025年12月GESP(C++二级): 黄金格
c++·算法·gesp·csp·信奥赛·二级·黄金格
Herbert_hwt3 小时前
C语言位操作符详解:从入门到实战应用
c语言·算法
ss2733 小时前
CompletionService:Java并发工具包
java·开发语言·算法
额呃呃3 小时前
select和poll之间的性能对比
开发语言·算法
王哈哈^_^3 小时前
【完整源码+数据集】道路交通事故数据集,yolo车祸检测数据集 7869 张,交通事故级别检测数据集,交通事故检测系统实战教程
人工智能·深度学习·算法·yolo·目标检测·计算机视觉·毕业设计