CodeCraft-22 and Codeforces Round #795 (Div. 2)

文章详细分析了四个不同的编程题目,涉及数组的操作以使相邻元素和为偶数,构造满足特定条件的排列,01串的子串和优化以及线段分组问题。每个问题的解答都包含了思路分析和代码实现,主要利用了位运算、前缀和、单调栈和并查集等数据结构和算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A. Beat The Odds

题目链接: Problem - A - Codeforces

样例输入:

2
5
2 4 3 6 8
6
3 5 9 7 1 3

样例输出:

1
0

题意:给定一个长度为n的数组,我们可以对这个数组进行操作,每次操作可以删除一个数,问至少操作多少次可以使得数组中任意两个相邻的数的和是偶数。

分析:只有奇数+奇数或者偶数+偶数才能等于偶数,所以题目就是问至少要删除多少个数才能使得数组中的数都是奇数或者都是偶数,所以我们只需要统计一下原来奇数和偶数的个数,输出较小值即可。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n,t;
		scanf("%d",&n);
		int ans=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&t);
			if(t&1) ans++;
		}
		printf("%d\n",min(ans,n-ans));
	}
	return 0;
} 

B. Shoe Shuffling

题目链接:Problem - B - Codeforces

样例输入: 

2
5
1 1 1 1 1
6
3 6 8 13 15 21

样例输出:

5 1 2 3 4 
-1

题意:给定一个长度为n的数组a[],让我们构造一个1~n的排列p[],使得p[i]!=i且a[p[i]]>=a[i].如果存在这样的排列就输出排列,否则输出-1.

分析:假如a[l~r]是相同的,如果l!=r,那么我们就可以令p[l]=r,r[l+1]=l,p[l+2]=l+1,……,p[r]=r-1.按照值相同的区间进行分段确定序列,如果l=r,那么就无解。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
int a[100010],p[100010];
int main()
{
	int T;
	cin>>T;
	int n;
	while(T--)
	{
		bool flag=true;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		for(int i=1;i<=n;i++)
		{
			int l=i,r=i+1;
			while(r<=n&&a[r]==a[l]) r++;
			r--;
			if(l==r)
			{
				flag=false;
				break;
			}
			p[l]=r;
			for(int j=l+1;j<=r;j++)
				p[j]=j-1;
			i=r;
		}
		if(flag)
		{
			for(int i=1;i<=n;i++)
				printf("%d ",p[i]);
			puts("");
		}
		else
			puts("-1");
	}
	return 0;
}

C. Sum of Substrings

题目链接:Problem - C - Codeforces

 样例输入:

3
4 0
1010
7 1
0010100
5 2
00110

 样例输出:

21
22
12

题意:给定一个长度为n的01串,定义di=si si+1组成的十进制数,f(s)=d1+d2+……+d(n-1)。我们可以对这个01串进行操作,每次操作可以交换两个相邻的数,问操作次数不大于k的情况下f(s)的最小值为多少。

分析:通过考虑每一位1的贡献可以发现,只要是1不位于两端,那么他的贡献就是11,无所谓1和谁相邻。所以我们要想减小贡献那么只能考虑把1移至两端,如果把1移至最右端那么1只能贡献1,如果把1移至最左端那么1只能贡献10,所以我们优先考虑把1移至最右端。然后接下来就是根据1的个数和位置来进行分情况讨论了,这个比较简单,细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10;
char s[N];
int main()
{
	int T;
	cin>>T;
	int ans=0;
	while(T--)
	{
		int n,k;
		int ans=0;
		scanf("%d%d%s",&n,&k,s+1);
		for(int i=2;i<=n;i++)
			ans+=(s[i-1]-'0')*10+s[i]-'0';
		int idl=1,idr=n;
		while(idl<=n&&s[idl]=='0') idl++;
		while(idr>=1&&s[idr]=='0') idr--;
		if(idr<1||idl>n)//代表没有1 
		{
			printf("%d\n",ans);
		}
		else if(idr==idl)//代表只有一个1
		{
			int cntr=n-idr;
			int cntl=idl-1;
			if(cntr<=k&&cntr!=0)
			{
				if(cntr==n-1)//说明唯一的1位于最左端 
					ans-=9;
				else
					ans-=10; 
			}
			else if(cntl<=k&&cntl!=0&&cntl!=n-1)
				ans-=1;
			printf("%d\n",ans);
		}
		else
		{
			int cntr=n-idr;
			int cntl=idl-1;
			if(cntr<=k&&cntr!=0)
			{
				k-=cntr;
				ans-=10;
			}
			if(cntl<=k&&cntl!=0)
				ans-=1;
			printf("%d\n",ans);
		}
	}
	
	return 0;
}

D. Max GEQ Sum

题目链接:Problem - D - Codeforces

样例输入:

3
4
-1 1 -1 2
5
-1 2 -3 2 -1
3
2 3 -1

样例输出:

YES
YES
NO

题意:给定一个长度为n的数组,如果该数组满足对于任意的<=i<=j<=n,都有max(a[i~j])>=a[i]+a[i+1]+……+a[j],那么就输出YES,否则输出NO.

分析:我们发现区间max的值最多只有n种情况,所以我们可以枚举每一个值作为区间最大值的区间,从该区间种找出一段包含最大值所在位置的区间使得该区间和最大,也就是最大化不等式右边的值,如果最大值都满足,那么该区间内的所有数都满足。比如说我们当前枚举a[i]为最大值,我们找到了l[i],r[i]分别是以a[i]为最大值的区间的最左边和最右边,那么我们随便从[l[i],i]中选择一个数作为区间左边界,然后从[i,r[i]]中选择一个数作为区间右边界,即满足区间最大值为a[i]。关键是我们现在就是要求解所有区间中的和的最大值。既然一定要包含a[i]我们可以分成两部分来求,一部分是在区间[l[i],i]找到一个p使得满足a[p]+a[p+1]+……+a[i]值最大,在区间[i+1,r[i]]中找到一个q使得满足a[i+1]+a[i+2]+……+a[q]值最大,区间加和我们可以用前缀和来优化,至于找最大值和最小值问题我们可以用ST表来解决。要想找到以某个数为最大值的左右区间的边界,我们可以直接用单调栈求解。

细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+6;
long long s[N],fx[N][32],fn[N][32];
int l[N],r[N],st[N],tt;
long long find_max(int L,int R)
{
	int len=(int)log2(R-L+1);
	return max(fx[L][len],fx[R+1-(1<<len)][len]);
}
long long find_min(int L,int R)
{
	int len=(int)log2(R-L+1);
	return min(fn[L][len],fn[R+1-(1<<len)][len]);
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%lld",&s[i]);
		tt=0;
		for(int i=1;i<=n;i++)
		{
			while(tt&&s[st[tt]]<=s[i]) tt--;
			if(tt) l[i]=st[tt]+1;
			else l[i]=1;
			st[++tt]=i;
		}
		tt=0;
		for(int i=n;i>=1;i--)
		{
			while(tt&&s[st[tt]]<=s[i]) tt--;
			if(tt) r[i]=st[tt]-1;
			else r[i]=n;
			st[++tt]=i;
		}
		tt=0;
		for(int i=2;i<=n;i++)//前缀和 
			s[i]+=s[i-1];
		for(int i=0;i<=n;i++)
			fx[i][0]=fn[i][0]=s[i];
		for(int len=1;len<=30;len++)
		for(int l=0;l-1+(1<<len)<=n;l++)
		{
			fx[l][len]=max(fx[l][len-1],fx[l+(1<<(len-1))][len-1]);
			fn[l][len]=min(fn[l][len-1],fn[l+(1<<(len-1))][len-1]);
		}
		bool flag=true;
		for(int i=1;i<=n;i++)//枚举最大值
		{
			long long mx=s[i]-s[i-1];
			long long lsum=s[i]-find_min(l[i]-1,i-1);
			long long rsum=max(find_max(min(i+1,r[i]),r[i])-s[i],0ll);//区间[i+1,r[i]]的数不是必选的,可以不选 
			if(rsum+lsum>mx)
			{
				flag=false;
				break;
			}
		}
		if(flag) puts("YES");
		else puts("NO");
	}
	return 0;
}

E. Number of Groups

题目链接:Problem - E - Codeforces

样例输入: 

2
5
0 0 5
1 2 12
0 4 7
1 9 16
0 13 19
3
1 0 1
1 1 2
0 3 4

样例输出:

2
3

题意:给定n条线段,每条线段有三个特征,一个是颜色c,一个是左端点l,另一个是右端点r,如果两条线段颜色不同且存在交集,那么他们就可以放在一组,假如线段i和线段j可以分为一组,线段j和线段k可以分为一组,那么线段i,j,j就可以分为一组。现在问这n条线段至少会被分为几组。

分析:通过题意描述我们可以发现分组具有传递性,显然是可以用并查集维护的,我们可以单独考虑每条红色线段和每条蓝色线段是否可以分为一组,这样总的复杂度就是O(n^2),显然是不行的。所以我们必须要对这个考虑优化,我们可以考虑把每条红色线段仅与与他相交且右端点位置最远的一条蓝色线段建立联系,这样总的连接数就是O(n),于是我们就可以把一条线段看成两个点,左端点看成入点,右端点看成出点,按照位置对点进行从小到大排序,相同位置按照入点在前的顺序进行排列,当遍历到左端点时,就把这条线段加入进集合,并找到与这条线段颜色不同且右端点最远的一条线段并建立联系,当遍历到右端点时就直接把这条线段删除即可。这样我们就可以建立与每条红色线段相关的蓝色线段。但是我们发现一个问题,就是我们只有红色线段之间的关系,并没有蓝色线段之间的关系,所以这样维护显然是会出问题的,我们需要维护蓝色线段之间的关系,所以当我们加入一个红色线段时,先看一下当前集合中的蓝色线段有多少,然后分别与当前的红色线段建立联系,每建立一次联系就把这个蓝色线段从集合中删除,直至剩一个右端点最大的,因为这些被删除的蓝色线段和最后剩余的蓝色线段都与当前的红色线段建立了联系,所以这些蓝色线段也通过并查集建立了联系。但是处在集合中的蓝色线段说明还没遇到其右端点,当遇到其右端点时还会被删除一次,但是不影响,顶多算是无效操作,按照这样的方法我们就能在最多建立n次联系的基础上找到他们的关系了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
#include<set>
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
int c[N],l[N],r[N],fu[N];
int find(int x)
{
	if(x!=fu[x]) return fu[x]=find(fu[x]);
	return x;
}
void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx!=fy) fu[fx]=fy;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d",&n);
		priority_queue<PII,vector<PII>,greater<PII> >q;
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d%d",&c[i],&l[i],&r[i]);
			fu[i]=i;
			q.push({l[i],-i});
			q.push({r[i],i});
		}
		set<PII> s[2];
		while(!q.empty())
		{
			PII t=q.top();
			q.pop();
			if(t.second>0)//区间右端点 
				s[c[t.second]].erase({r[t.second],t.second});
			else//区间左端点 
			{
				int id=t.second*(-1);
				s[c[id]].insert({r[id],id});
				while(s[1^c[id]].size()>1)//保证相同颜色线段可以合并 
				{
					merge(id,s[1^c[id]].begin()->second);
					s[1^c[id]].erase(s[1^c[id]].begin());
				}
				if(s[1^c[id]].size()==1) merge(id,s[1^c[id]].begin()->second);
			}
		}
		int ans=0;
		for(int i=1;i<=n;i++)
			if(find(i)==i) ans++;
		printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值