Codeforces Round 932 (Div. 2)

A. Entertainment in MAC(Problem - A - Codeforces)

题目大意:现有一个字符串和一个偶数n,我们需要对字符串执行n次操作,每次操作有两种选择,一种是将字符串反转然后接在原字符串后面,一种是仅仅将字符串反转。需要输出进行n次操作后字典序最小的字符串。

思路:显然字符串应该越短越好,偶数次操作,如果只用来反转,那么实际相当于没有进行操作,如果进行一次反转再进行一次反转拼接,那么实际上就改变了顺序,拼接太多次没有意义,最多拼接一次就够了。那么就比较一下这两种方式得到的字符串的大小即可。实际上我们只比较原字符串和反转后的字符串即可。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		string s,tmp;
		int n;
		cin>>n>>s;
		tmp=s;
		reverse(s.begin(),s.end());
		if(tmp<=s) cout<<tmp<<endl;
		else cout<<s<<tmp<<endl;
	}
}

B. Informatics in MAC(Problem - B - Codeforces

题目大意:现在有一个数组,我们需要把它分成若干段,保证每一段的mex值都相同。输出这若干个区间的左右边界。

思路:首先如果数组中没有0,那么显然怎么划分都可,我们随意输出即可。如果数组中有0,那么就要保证每一段中都有0,如果非要保证每一段中有且仅有一个0就太麻烦了,我们可以就将数组划分成两段,从0开始连续出现的数肯定要在两个区间中都出现,我们先从头遍历,给第一个区间找到所有必须出现的数,然后剩下的数都是第二个区间中的,如果第二个区间中所有必须出现的数都有,那么就成立,否则就不成立。

#include<bits/stdc++.h>
using namespace std;
int a[100010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		scanf("%d",&n);
		map<int,int>mp;
		for(int i=1;i<=n;i++) 
		{
			int x;
			scanf("%d",&x);
			mp[x]++;
			a[i]=x;
		}
		if(!mp.count(0))
		{
			printf("2\n");
			printf("1 1\n");
			printf("2 %d\n",n);
		}
		else
		{
			int mx=-1;
			for(auto it:mp)
			{
				if(it.first==mx+1) mx++;
				else break;
			}
			int i;
			set<int>s;
			for(i=1;i<=n;i++)
			{
				if(a[i]<=mx) s.insert(a[i]);
				if(s.size()==mx+1) break;
			}
			int k=i;
			s.clear();
			for(i=k+1;i<=n;i++)
			{
				if(a[i]<=mx) s.insert(a[i]);
			}
			if(s.size()==mx+1) 
			{
				printf("2\n");
				printf("1 %d\n",k);
				printf("%d %d\n",k+1,n);
			}
			else printf("-1\n"); 
		}
	}
}

C. Messenger in MAC(Problem - C - Codeforces

题目大意:现在有n条消息,每条消息有两个花费ai,bi,我们要从中选取若干条消息(不用按顺序),给选出的消息一个下标p,要使得sum(a[p])+sum(abs(b[p]-b[p-1]))的值小于l,问最多能选多少个消息。

思路:我们来分析一下,对于a[i]没什么说的,就是加起来,对于b[i],由于求的是绝对值,所以我们可以最小化b,也就是将b排序,然后实际产生贡献的就是最大的和最小的,对结果的贡献是两者的差值。那么该如何选呢?我最开始想的是显然a对结果的影响大一些,所以我们将a排序,然后最开始将所有的都选上,然后一个一个弹出,弹到符合要求为止,但是这个思路下面这个样例没法过:

3 12

4 8

2 1

2 12

 第一个由于a=4,肯定最先被弹出,但是这题最多的情况就是选第一个和第三个。

这题如果暴搜的话时间复杂度又太高了。

其实陷入了一个误区,我们已经分析出了最关键的一个条件,对b进行排序后可以最小化b的结果,所以现在的问题就是如何选择区间,虽然我们不能暴搜枚举出所有的选择状态,但是我们实际可以枚举区间。具体一点就是,我们去枚举区间的左右端点,对于一个左端点,枚举它的所有右端点,维护一个总和,同时用一个优先队列或是多集来维护被选区间中的a和一个变量sum来维护被选a的值,当br-bl+sum>l的时候就把最大的a弹出。如果弹出的是中间的值,那么就无所谓,反正本来所选的部分就不用连续,但是如果弹出了左右边界,那么我们维护的br-bl岂不是失效了,假设我们真的弹出了左右边界,那么当前的左边界应该是l+c,右边界应该是r-d,这个区间我们在设定左边界为l+c的时候会遍历到,而且因为区间缩小了,左右边界更近了,所以实际的br-bl可能会变小,这里用一个更大的值来表示,不会多算,但是却可以帮我们访问所有的情况。显然区间固定的时候,弹出较大的a是可行的策略。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,l;
		scanf("%d%d",&n,&l);
		vector<pair<int,int>>p;
		for(int i=1;i<=n;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			p.push_back({b,a});
		}
		sort(p.begin(),p.end());
		int ans=0;
		for(int i=0;i<n;i++)
		{
			int sum=0;
			multiset<int>s;
			for(int j=i;j<n;j++)
			{
				int a=p[j].second,b=p[j].first;
				sum += a;
				s.insert(a);
				while(s.size()&&p[j].first-p[i].first+sum>l) 
				{
					int tmp=*s.rbegin();
					sum -= tmp;
					s.extract(tmp);
				}
				ans=max(ans,(int)s.size());
			}
		}
		printf("%d\n",ans);
	}
}

ps:这题很巧妙,很明显是有冗余的,而且一些特殊情况会影响我们表达式的正确性,但是冗余的影响实际上是不会干扰结果的。

D. Exam in MAC(Problem - D - Codeforces

题目大意:现给定一个大小为n的集合s和一个数c,要求选择两个数x,y,满足0<=x<=y<=c,使得x+y不在集合中,y-x也不在集合中。(有t组测试样例,t<=2e4,1<=n<=3e5,1<=c<=1e9)

思路:如果暴力的话,那么就是枚举x,y,但是c的范围太大了,显然是会超时的,所以我们要换个思路,而且还要注意到t和n的范围,我们尽量要在线性的时间复杂度内解决。既然不能直接枚举x,y判断是否符合要求,那么我们是否可以在要求的时间内得到不符合要求的点对数呢,因为很显然我们是可以得到点对的总数的,那么排除不合法的剩下的不就是合法的。我们是可以枚举集合中的每个数的,那么能否通过枚举集合中的数,将不符合要求的点对的数量统计出来呢?如果只讨论数量而不讨论具体是什么的话,显然是会重复的。

比如1,2,3我们讨论对每个数不符合要求的数(c=3):1+2+3+4=10

1:(0,1)         | (0,1)(1,2)(2,3) 

2:(0,2)(1,1)  | (0,2)(1,3) 

3:(0,3)(1,2)  | (0,3) 

我们如果只统计个数的话,(1,2)在1和3中都出现了,我们就会讨论重复

那么重复讨论的问题该怎么避免呢?我们来看为什么会重复,因为(1,2)在求和成立的时候被算了一次,在求差成立的时候又被算了一次,所以就算多了产生重复。那么我们实际上只要统计出哪些数既求和成立又求差成立,把这些数加上即可。

我们一个一个看,求和成立的话可以找出多少对呢?
对于s[i],x+y=s[i],x<=y,那么就会又s[i]/2+1对,因为从0开始计算

求差成立的话有多少对呢?
对于s[i],y-x=s[i],0<x<=y<=c,所以s[i]<=y<=c,那么就有c-s[i]+1对

都成立的话又该怎么算呢?
x+y=s[i],y-x=s[j],y=(s[i]+s[j])/2,x=(s[i]-s[j])/2,这个式子看似麻烦,似乎需要O(n^2)的时间复杂度才能解决,但是我们仔细思考一下,s[i]和s[j]的奇偶性有什么关系,显然两者奇偶性应该相同,因为x和y都是整数,那么我们统计出奇数和偶数各自的个数岂不是就可以得到它们可以产生的组合数。然后问题就解决了。 

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,c;
		scanf("%d%d",&n,&c);
		long long res = (long long)(c+2)*(c+1)/2;
		int j=0,o=0;
		for(int i=1;i<=n;i++)
		{
			int x;
			scanf("%d",&x);
			res -= x/2+1;
			res -= c-x+1;
			if(x%2) j++;
			else o++;
		}
		res += (long long)j*(j+1)/2;
		res += (long long)o*(o+1)/2;
		printf("%lld\n",res);
	}
}
  • 26
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值