Codeforces Round 932 (Div. 2)(A,B,C,D)

news/2024/5/20 9:54:35 标签: 算法, 数据结构, , 动态规划, 容斥原理, 数论

比赛链接

AB都是思维,更确切地说,A考了字符串字典序,很经典的贪心考点,B考了MEX运算。C出的还是比较好的,dp方法值得学习。D题是个不太好想的容斥,主要是变量有点多,容易搞混。


A. Entertainment in MAC

题意:

恭喜你,你被硕士援助中心录取了!但是,你在课堂上感到非常无聊,厌倦了无所事事,于是你给自己想了一个游戏。

给你一个字符串 s s s 和一个整数 n n n 。你可以对它进行两种运算:

  1. 将反向字符串 s s s 添加到字符串 s s s 的末尾(例如,如果 s = s = s= cpm,那么在执行操作后 s = s = s= cpmmpc )。
  2. 将当前字符串 s s s 倒转(例如,如果 s = s = s= cpm,则在执行操作后 s = s = s= mpc)。

需要确定在进行精确 n n n 操作后,可以得到的词序最小的 † ^{\dagger} 字符串。请注意,您可以按照任意顺序进行不同类型的运算,但必须总共进行 n n n 次运算。

† ^{\dagger} 当且仅当以下条件之一成立时,字符串 a a a 在词法上比字符串 b b b 小:

  • a a a b b b 的前缀,但是 a ≠ b a \ne b a=b
  • a a a b b b 不同的第一个位置上,字符串 a a a 的字母在字母表中出现的时间早于 b b b 中的相应字母。

思路:

发现新构造出来的字符串最前面 ∣ s ∣ |s| s 个字符要么是 s s s,要么是反向的 s s s。正向好说,不用操作就是。而要保证进行偶数次操作后,最前面是反向的 s s s,那么一定需要先用一下操作 2 2 2,再用一下操作 1 1 1

为了字典序最小,我们后面一定是一直使用操作 2 2 2。因此我们只需要比较一下正向 s s s 和反向 s s s 哪个小,如果是正向的,那么就只用操作 2 2 2,否则先用一下操作 1 1 1,之后一直用操作 2 2 2

code:

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

int T,n;
string s,t;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>s;
		t=string(s.rbegin(),s.rend());
		if(s<=t)cout<<s<<endl;
		else cout<<t+s<<endl;
	}
	return 0;
} 

B. Informatics in MAC

题意:

在硕士援助中心,Nyam-Nyam 接受了一项信息学方面的家庭作业。

有一个长度为 n n n 的数组 a a a ,你想把它分成 k > 1 k\gt 1 k>1 个子段 † ^{\dagger} ,使每个子段上的 MEX ⁡ ‡ \operatorname{MEX} ^{\ddagger} MEX 都等于相同的整数。

请帮助 Nyam-Nyam 找到合适的除法,或者确定不存在合适的除法。

† ^{\dagger} 将数组划分为 k k k 个子数段的定义是 k k k 对整数 ( l 1 , r 1 ) , ( l 2 , r 2 ) , … , ( l k , r k ) (l_1, r_1), (l_2, r_2), \ldots, (l_k, r_k) (l1,r1),(l2,r2),,(lk,rk) ,使得 l i ≤ r i l_i \le r_i liri 和每个 1 ≤ j ≤ k − 1 1 \le j \le k - 1 1jk1 l j + 1 = r j + 1 l_{j + 1} = r_j + 1 lj+1=rj+1 ,以及 l 1 = 1 l_1 = 1 l1=1 r k = n r_k = n rk=n 。这些对表示子段本身。

数组中的 ‡ MEX ⁡ ^{\ddagger}\operatorname{MEX} MEX 是不属于该数组的最小非负整数。

例如

  • 数组 [ 2 , 2 , 1 ] [2, 2, 1] [2,2,1] MEX ⁡ \operatorname{MEX} MEX 0 0 0 ,因为 0 0 0 不属于该数组。
  • 数组 [ 3 , 1 , 0 , 1 ] [3, 1, 0, 1] [3,1,0,1] 中的 MEX ⁡ \operatorname{MEX} MEX 2 2 2 ,因为 0 0 0 1 1 1 属于数组,而 2 2 2 不属于数组。
  • 数组 [ 0 , 3 , 1 , 2 ] [0, 3, 1, 2] [0,3,1,2] 中的 MEX ⁡ \operatorname{MEX} MEX 4 4 4 ,因为 0 0 0 1 1 1 2 2 2 3 3 3 属于数组,而 4 4 4 不属于数组。

思路:

每段区间都不含某个数,说明这个数在整段区间上肯定从来没出现过。所以我们要选就选在原序列中没出现的数。我们要一段区间上的 M E X MEX MEX 等于某个数 x x x,就需要这段区间上包含 1 ∼ x − 1 1\sim x-1 1x1 的所有数。所以我们要每段区间的 M E X MEX MEX 值都等于某个数 x x x 的话, x x x 只可能是最小的原序列没出现的数。我们只要对这个数验证一下即可。

code:

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

int T,n;
int a[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		set<int> S;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			S.insert(a[i]);
		}
		int x;
		for(int i=0;;i++)
			if(S.find(i)==S.end()){
				x=i;
				break;
			}
		
		vector<pair<int,int> > ans;
		for(int l=1,r=1,t;l<=n;){
			t=0;
			S.clear();
			do{
				S.insert(a[r++]);
				while(S.find(t)!=S.end())t++;
				if(r>n && t!=x){//将最后一段不完整的段归入到前一段
					l=ans.rbegin()->first;
					ans.pop_back();
					break;
				}
//				cout<<l<<" "<<r<<endl;
			}while(t!=x);
			ans.push_back(make_pair(l,r-1));
			l=r;
		}
		
		if(ans.size()>1){
			cout<<ans.size()<<endl;
			for(auto x:ans)
				cout<<x.first<<" "<<x.second<<endl;
		}
		else cout<<-1<<endl;
	}
	return 0;
}

C. Messenger in MAC

题意:

凯夫特梅鲁姆硕士生援助中心的新信使计划进行一次更新,开发人员希望在更新中优化向用户显示的信息集。目前共有 n n n 条信息。每条信息由两个整数 a i a_i ai b i b_i bi 表示。读取数字为 p 1 , p 2 , … , p k p_1, p_2, \ldots, p_k p1,p2,,pk 1 ≤ p i ≤ n 1 \le p_i \le n 1pin ,所有 p i p_i pi 都是不同的)的信息所花费的时间由公式计算得出:

∑ i = 1 k a p i + ∑ i = 1 k − 1 ∣ b p i − b p i + 1 ∣ \Large \sum_{i=1}^{k} a_{p_i} + \sum_{i=1}^{k - 1} |b_{p_i} - b_{p_{i+1}}| i=1kapi+i=1k1bpibpi+1

请注意,读取由一个编号为 p 1 p_1 p1 的报文组成的报文集所需的时间等于 a p 1 a_{p_1} ap1 。此外,读取一组空信息所需的时间为 0 0 0

用户可以确定他愿意在信使中花费的时间 l l l 。信使必须告知用户信息集的最大可能大小,其阅读时间不超过 l l l 。请注意,信息集的最大大小可以等于 0 0 0

流行信使的开发人员未能实现这一功能,因此他们要求您解决这一问题。

思路1():

上面的描述很神秘,实际上表达的意思很简单。就是可以乱序地不重复地成对选出一些 a , b a,b a,b,使得选出数的序列的 a a a 的和以及相邻两项 b b b 的差的绝对值相等。总和不超过 L L L 的前提下,使得选出的数尽可能多。

为了最小化 ∑ i = 1 k − 1 ∣ b p i − b p i + 1 ∣ \sum_{i=1}^{k - 1} | b_{p_i} - b_{p_{i+1}}| i=1k1bpibpi+1 我们不妨把每对数按 b b b 来进行排序,按顺序选出若干个数后,因为有大小关系,绝对值号可以直接拆掉,前一项与后一项相消,也就是 b p k − b p k − 1 + b p k − 1 − b p k − 2 + ⋯ + b p 2 − b p 1 b_{p_k}-b_{p_{k-1}}+b_{p_{k-1}}-b_{p_{k-2}}+\dots+b_{p_{2}}-b_{p_{1}} bpkbpk1+bpk1bpk2++bp2bp1 那么它就变成了 b p k − b p 1 b_{p_k}-b_{p_{1}} bpkbp1。把 p 1 p_1 p1 看成选取区间的左端点, p k p_k pk 看成右端点的话,则原式就相当于在区间 [ l , r ] [l,r] [l,r] 中选取若干个数,使得 a i a_i ai 之和加上 r − l r-l rl 小于等于 L L L(左右端点必选),问最多能选多少个数。

朴素的想法是先枚举右端点,然后枚举左端点,然后贪心地选区间内最小的前几个数。但是这样最坏是 O ( n 3 l o g   n ) O(n^3log\,n) O(n3logn) 的(区间排序占 O ( n l o g   n ) O(nlog\,n) O(nlogn))。我们希望有个 O ( n 2 l o g   n ) O(n^2log\,n) O(n2logn) 的做法。所以我们考虑能否从上一个区间直接推过来。

一个想法是:枚举右端点 r r r,然后左端点 l l l r r r 1 1 1 推。使用来存储区间 [ l , r ] [l,r] [l,r] 中最小的若干个值,使得它们的和 ≤ L − ( r − l ) \le L-(r-l) L(rl)(这意味着我们以 l , r l,r l,r 为左右端点的时候,选取的就是中的数,因为总和满足前面的条件,因此在值上是满足条件的)。

不过这个想法有个比较明显的缺陷,就是有可能我们推到某个左端点 l l l 根本没选 a l a_l al,而是直接将它踢出了,这样就不符合左右端点必选的条件了。不过这样还是可以得到正确答案的,因为这种不符合条件的情况一定无法更新答案

为什么呢?假设我们推到了某个左端点 l l l 却没有选上 a l a_l al,那么我们此时中最靠左的数的位置 l ′ l' l 作为左端点时,中数的总和加上 r − l ′ r-l' rl 一定更小,比这个情况更有可能更新答案。前面的情况已经更新过答案了,因此这个情况一定无法更新答案。这就保证了正确性。

code:

#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=2005;

int T,n;
ll L;
pair<int,int> x[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n>>L;
		for(int i=1;i<=n;i++){
			cin>>x[i].second>>x[i].first;
		}
		sort(x+1,x+n+1);
		
		int ans=0;
		for(int r=1;r<=n;r++){
			priority_queue<int> h;
			ll tot=0;
			for(int l=r;l>=1;l--){
				h.push(x[l].second);
				tot+=x[l].second;
				while(tot+x[r].first-x[l].first>L && !h.empty()){
					tot-=h.top();
					h.pop();
				}
				if(tot+x[r].first-x[l].first<=L)
					ans=max(ans,(int)h.size());
			}
		}
		cout<<ans<<endl;
	}
	return 0;
} 

思路2(动态规划):

我们在选数的时候需要知道那些数是第一个选上的数,哪一个是最后一个选上的数,所以设 d p 1 [ i ] [ j ] dp1[i][j] dp1[i][j] 表示前 i i i 个数中选出 j j j 的最小值, d p 2 [ i ] [ j ] dp2[i][j] dp2[i][j] 表示前 i i i 个数中选出 j j j 个,并且最后一个数选 a j a_j aj 的最小值。

d p 1 dp1 dp1 我们就正常选数即可,只需要特判一下当前状态选择的是否是第一个数, d p 2 dp2 dp2 d p 1 dp1 dp1 来推,这样 d p 2 dp2 dp2 得到的就是一种选取方式,即若干个 a a a 的值减去 b l b_l bl 再加上 b r b_r br

而第一维为 i i i d p 1 , d p 2 dp1,dp2 dp1,dp2 之和 i − 1 i-1 i1 的有关,所以我们可以优化掉第一维。这样就变成了 jiangly 大佬的写法了。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <set> 
using namespace std;
typedef long long ll;
const ll inf=1e18;
const int maxn=2005;

int T,n;
ll l;
pair<int,int> itv[maxn];


int main(){
	cin>>T;
	while(T--){
		cin>>n>>l;
		for(int i=1;i<=n;i++)
			cin>>itv[i].second>>itv[i].first;
		sort(itv+1,itv+n+1);
		
		vector<ll> dp1(n+1,inf),dp2(n+1,inf);
		for(int i=1;i<=n;i++){
			ll a=itv[i].second;
			ll b=itv[i].first;
			for(int j=n;j>=1;j--){
				dp1[j]=min(dp1[j],dp1[j-1]+a);
				dp2[j]=min(dp2[j],dp1[j-1]+a+b);
			}
			dp1[1]=min(dp1[1],a-b);
			dp2[1]=min(dp2[1],a);
		}
		for(int i=n;i>=1;i--){
			if(dp2[i]<=l){
				cout<<i<<endl;
				break;
			}
			if(i==1){
				cout<<0<<endl;
			}
		}
	}
	return 0;
}

D. Exam in MAC

题意:

硕士生援助中心公布了入学考试,考试内容如下。

给考生一个大小为 n n n 的集合 s s s 和一个奇怪的整数 c c c 。对于这个集合,需要计算出使 0 ≤ x ≤ y ≤ c 0 \leq x \leq y \leq c 0xyc x + y x + y x+y 包含在集合 s s s 中,并且 y − x y - x yx包含在集合 s s s 中, ( x , y ) (x, y) (x,y) 对的个数。

你的朋友想进入中心工作。请帮助他通过考试!

思路:

容斥原理。很好的题。

x , y x,y x,y 是任取的,我们当然不可能任取,用屁股想都会超时。因此从 x + y , y − x x+y,y-x x+y,yx 下手。假设 x + y = a ∉ s , y − x = b ∉ s x+y=a\not\in s,y-x=b\not\in s x+y=as,yx=bs,则有 x = a − b 2 , y = a + b 2 x=\dfrac{a-b}2,y=\dfrac{a+b}2 x=2ab,y=2a+b。显然 b ≤ a b\le a ba,并且当 a , b a,b a,b 不同时,得到的 x , y x,y x,y 一定不同(也就说有多少对不同的 a , b a,b a,b,就有多少对不同的 x , y x,y x,y)。

题目说 x + y , y − x x+y,y-x x+y,yx 不在集合 s s s 中,也就是 a , b a,b a,b 不在集合 s s s 中。我们只要找到所有不在集合 s s s 中的数,让 a , b a,b a,b 去等于这些数,就能得到一对对 x , y x,y x,y。但是问题在于如何保证得到的 x , y x,y x,y 合法,以及如何计数。

一开始的想法是因为 a − b 2 ≤ a + b 2 ≤ c \dfrac{a-b}2\le \dfrac{a+b}2 \le c 2ab2a+bc,所以枚举 a a a,然后去算 b b b 的个数,不过还是太麻烦了,当 c c c 很大的时候还需要将一整段进行压缩,整段求和,暴力来算会T。

所以尝试反着来求。用总的对数减去 a a a(也就是 x + y x+y x+y) 合法的对数和 b b b(也就是 y − x y-x yx) 合法的对数,最后再加上 a , b a,b a,b 同时合法的对数,剩下的就是不合法的对数。

  1. 总对数显然是 C c + 1 2 = ( c + 1 ) ∗ ( c + 2 ) / 2 C_{c+1}^2=(c+1)*(c+2)/2 Cc+12=(c+1)(c+2)/2。( x , y x,y x,y 可选 0 ∼ c 0\sim c 0c 的数,包括 0 0 0

  2. 我们让 a a a 去等于已有的数,得到的就是 a a a 合法的对数。假设这个数是 t t t,那么 x + y = t x+y=t x+y=t ,又因为 y ≥ x y\ge x yx,所以得到的总的合法对数为 t / 2 + 1 t/2+1 t/2+1

  3. 同理,我们让 b b b 去等于已有的数,得到的就是 b b b 合法的对数。假设这个数是 t t t,那么 y − x = t y-x=t yx=t ,因为 y ≤ t y\le t yt,所以得到的总的合法对数为 c − t + 1 c-t+1 ct+1

  4. a , b a,b a,b 同时合法的话,需要 x = a − b 2 , y = a + b 2 x=\dfrac{a-b}2,y=\dfrac{a+b}2 x=2ab,y=2a+b 算出来的 x , y x,y x,y 为正整数,因此 a , b a,b a,b 需要奇偶性相同。我们统计一下给出的数中有多少个奇数 o o o 和偶数 e e e,总数就是 C e 2 + C o 2 = e ∗ ( e + 1 ) / 2 + o ∗ ( o + 1 ) / 2 C_e^2+C_o^2=e*(e+1)/2+o*(o+1)/2 Ce2+Co2=e(e+1)/2+o(o+1)/2

code:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

int T,n,c;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>c;
		ll ans=1ll*(c+1)*(c+2)/2;
		int o=0,e=0;
		for(int i=1,t;i<=n;i++){
			cin>>t;
			ans-=t/2+1;//x+y=t
			ans-=c-t+1;//y-x=t
			if(t&1)o++;
			else e++;
		}
		ans+=1ll*o*(o+1)/2;
		ans+=1ll*e*(e+1)/2;
		cout<<ans<<endl;
	}
	return 0;
}

http://www.niftyadmin.cn/n/5463184.html

相关文章

AGI时代,LLM可以在AutoML哪些环节进行增强?

当下大模型技术发展如火如荼&#xff0c;颇有改变各行业和各领域的架势。那么对于AutoML来讲&#xff0c;LLM对其有哪些助力&#xff1f;对于这个问题&#xff0c;我们来问一问kimi chat&#xff0c;看看它怎么回答&#xff1f; 大型语言模型&#xff08;LLM&#xff09;可以在…

如何安装OceanBase的OBD

选择一&#xff1a;借助 all-in-one 安装包安装 OBD&#xff08;推荐&#xff09; OceanBase 社区版的all-in-one安装包是一个集成了多种工具的一键式安装包。它包含了数据库软件本身&#xff0c;以及OBD、OBProxy、OBClient&#xff0c;自4.1版本起&#xff0c;还额外加入了O…

SparkSQL基本数据抽象RDD/DataFrame/Dataset介绍[附操作代码]

文章目录 1. SparkSQL1.1 总述1.2 数据格式1.3 转化关系1.3.1 RDD转DataFrame | Dataset1.3.2 DataFrame转Dataset1.3.3 DataFrame | Dataset转RDD1.3.4 Dataset转DataFrame 2. DataFrame 数据导入2.1 准备工作pom.xmllog4j.properties 2.2 RDD转换DataFrame2.2.1 模式12.2.2 模…

一文带你快速了解Flume!

Flume定义 Apache Flume 是一个分布式、可靠且高可用的系统&#xff0c;用于有效地收集、聚合和移动大量日志数据到集中式数据存储。它专为日志数据收集服务设计&#xff0c;但也适用于各种其他数据流场景&#xff08;但主要还是.txt文本文件&#xff09;。 Flume 支持多种数…

如何还原系统,电脑系统还原怎么操作

很多用户可能是电脑新手,对于电脑还原系统这项功能并不是十分了解,所以下面跟大家聊聊有关电脑系统还原的小知识。Win10系统有还原功能,当电脑出现故障,你又无法解决时,就可以采用系统还原的方式让电脑恢复正常运行的状态。那么win10系统的还原功能在哪里,又是如何进行系…

从0开始搭建基于VUE的前端项目(二) 安装和配置element-ui组件库

版本和地址 ElementUI 2.15.14 (https://element.eleme.io/)按需引入的插件 babel-plugin-component(1.1.1) https://github.com/ElementUI/babel-plugin-component 安装 npm install element-ui完整引入(不建议) 这种方式最后打包的源文件很大&#xff0c;造成网络资源的浪…

python爬虫基础------文件的相关操作(第十天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

【linux】AMD GPU和NVIDIA GPU驱动安装

AMD GPUs - Radeon™ PRO W7900的驱动安装过程 要在Linux系统上安装AMD的Radeon™ PRO W7900显卡驱动程序&#xff0c;通常需要执行以下步骤。以下示例基于Ubuntu系统&#xff1b;其他Linux发行版的具体步骤可能有所不同。 1. 更新系统 打开一个终端窗口&#xff0c;并输入…