Codeforces Round #821 (Div. 2)

A. Consecutive Sum

题目链接:Problem - A - Codeforces

样例输入:

5
3 2
5 6 0
1 1
7
5 3
7 0 4 0 4
4 2
2 7 3 4
3 3
1000000000 1000000000 999999997

样例输出:

11
7
15
10
2999999997

题意:给定一个长度为n的数组,我们可以进行不超过k次操作,每次操作可以选择一个满足i%k=j%k的数对(i,j),并交换a[i]和a[j],进行完所有的操作后我们选定一个长度为k的区间并以其内元素的和作为该区间的值,输出我们能够得到的最大值。

分析:我们只需要统计一下模k下的数的最大值即可,因为通过一次操作我们一定可以把模k等于j的位置上的最大的数移至第j个位置,所以k次操作后我们一定可以使得最大的数都集中在一个长度为k的区间内,所以我们只需要统计一下对于所有的j在区间[0,k-1]上的最大值并相加即可。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
int a[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n,k;
		scanf("%d%d",&n,&k);
		for(int i=0;i<k;i++) a[i]=0;
		for(int i=1;i<=n;i++)
		{
			int t;
			scanf("%d",&t);
			a[i%k]=max(a[i%k],t);
		}
		long long ans=0;
		for(int i=0;i<k;i++) ans+=a[i];
		printf("%lld\n",ans);
	}
	return 0;
} 

B. Rule of League

题目链接:Problem - B - Codeforces

样例输入: 

5
5 2 0
8 1 2
3 0 0
2 0 1
6 3 0

样例输出:

1 1 4 4
-1
-1
2 
-1

题意:给定一个n,代表有n个人,一开始1号和2号比赛,获胜的人与3号比赛,依次类推,问有没有可能构造一种获胜方案使得n个玩家的任何一个玩家要么获胜x次要么获胜y次,如果有就输出获胜方案,否则输出-1。

分析:n个人一共进行n-1场比赛,每场比赛只有一个获胜者,那么至少有一个人从来没有获胜过,所以x和y中至少有一个0才有可能构造成功。这里假设y不等于0,其实我们发现当一个玩家获胜后,那么他需要连胜y次,所以我们还要保证n-1是y的倍数才能进行构造,构造方法就是一个人获胜就直接连胜y次即可。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n,x,y;
		scanf("%d%d%d",&n,&x,&y);
		if(x>y) swap(x,y);
		if(x) puts("-1");
		else if(y==0) puts("-1");
		else
		{
			if((n-1)%y) puts("-1");
			else
			{
				int t=1,cnt=0;
				for(int i=2;i<=n;i++)
				{
					printf("%d ",t);
					cnt++;
					if((cnt%y)==0) t=i+1;
				}
				puts("");
			}
		}
	}
	return 0;
}

C. Parity Shuffle Sorting

题目链接:Problem - C - Codeforces

样例输入: 

3
2
7 8
5
1 1000000000 3 0 5
1
0

样例输出:

0
2
3 4
1 2
0

题意:给定一个长度为n的数组,我们每次可以选定数组中的两个数a[l]和a[r]进行操作,如果a[l]+a[r]是一个奇数,那么就令a[r]=a[l],否则令a[l]=a[r],让我们输出一种操作方案使得操作后的数组中的元素是单调不递减的,操作次数不能大于n。

分析:这个题就是直接模拟即可,我们能够发现一个策略就是我们先用一次操作把a[1]和a[n]能变成同一个数,当然如果一开始两者就相同我们就可以忽略这个操作,然后对于每个i属于区间[2,n-1],如果a[1]+a[i]是一个奇数,那么我们就对1,i两个数进行一次操作,这样就能使得a[i]=a[1],若a[1]+a[i]是一个偶数,那么a[n]+a[i]也是一个偶数,那么我们就对i,n两个数操作使得a[i]=a[r],按照这个策略我们最多操作n-1次就可以使得整个数组的值相同。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
int a[N];
queue<pair<int,int> >q;
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		if(a[1]!=a[n])
		{
			q.push({1,n});
			if((a[1]+a[n])&1)
				a[n]=a[1];
			else
				a[1]=a[n];
		}
		for(int i=2;i<n;i++)
		{
			if((a[1]+a[i])&1)
				q.push({1,i});
			else
				q.push({i,n});
		}
		printf("%d\n",q.size());
		while(!q.empty())
		{
			pair<int,int> t=q.front();
			q.pop();
			printf("%d %d\n",t.first,t.second);
		}
	}
	return 0;
}

D1. Zero-One (Easy Version)

题目链接:Problem - D1 - Codeforces

样例输入: 

4
5 8 7
01001
00101
5 7 2
01000
11011
7 8 3
0111001
0100001
5 10 1
01100
01100

样例输出:

8
-1
6
0

题意:给定一个n,接着给出两个长度为n的01字符串,我们可以对a字符串进行操作,每次操作我们可以选择一个数对(l,r)并将这两个位置的数取反,如果l和r是相邻的两个数那么代价就是x,否则代价是y,现在有y<=x,问我们通过操作使得a字符串变成b字符串的最小代价,如果无法变为b字符串就输出-1.

分析:首先我们先用vector存一下a串和b串数值不相同的那些下标,可以发现由于我们每次操作都是对一个数对进行操作,所以说如果要是vector的大小是一个奇数那么肯定是无解的,所以下面我们只需要讨论vector大小是偶数的情况。由于y<=x,也就是说我们应该优先操作两个不相邻的字符,这样代价一定是最小的,不妨假设vector的大小是4,那么就是a[0],a[1],a[2],a[3],我们可以先对a[0]和a[2]进行操作,后对a[1]和a[3]进行操作,这样我们一定是可以使得每次操作都是在不相邻的两个数之间进行的,容易发现对于大小大于4的偶数情况也是同理的,所以这种情况的最小代价一定是(size/2)*y,但是我们需要特殊考虑一种情况就是大小为2的情况,如果这两个数不相邻,那么我们直接输出y是没问题的,但如果这两个数要是相邻呢?直接输出x吗?其实不一定,因为我们可以完全可以找一个中间点进行间接操作使得达到这两个相邻点操作的目的,比如a[0]=2,a[1]=3,我们可以找一个5,先让2和5操作,再让3和5操作,这样也可以达到目的,这样的代价是2*y,这个代价是有可能比x小的,所以我们只需要对这种情况特殊判断一次即可。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
char a[N],b[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		long long n,x,y;
		scanf("%lld%lld%lld",&n,&x,&y);
		scanf("%s",a+1);
		scanf("%s",b+1);
		vector<int>p;
		for(int i=1;i<=n;i++)
			if(a[i]!=b[i]) p.push_back(i);
		if(p.size()&1) puts("-1");
		else if(p.size()==2)
		{
			if(p[0]+1==p[1])
			{
				if(n==2) printf("%lld\n",x);
				else printf("%lld\n",min(x,2*y));
			}
			else
				printf("%lld\n",y);
		}
		else
			printf("%lld\n",y*(p.size()/2));
	}
	return 0;
}

D2. Zero-One (Hard Version)

题目链接:Problem - D2 - Codeforces

样例输入:

6
5 8 9
01001
00101
6 2 11
000001
100000
5 7 2
01000
11011
7 8 3
0111001
0100001
6 3 4
010001
101000
5 10 1
01100
01100

样例输出:

8
10
-1
6
7
0

题意同D1相同,这里就不说了,与D1最大的区别就是这里没有说明y和x的大小关系。

分析:由于上面的做法是建立在y<=x的基础上的,我们优先选择不相邻的两个点进行交换,那么这道题我们只需要讨论y>x的情况即可,这样我们应该是尽可能使得相邻的两个点进行交换了,但是发现没法直接根据x和y的关系来判断两个不相邻位置到底是应该直接进行交换还是应该先交换至一起再进行交换,所以这里选择使用区间DP进行求解。

注意由于我们已经用vector存下了a串和b串中字符不同的位置,所以下列坐标均是相对于vector的坐标而言的。

f[i][j]表示将i~j匹配成功所需要的最小代价,那么答案就是f[0][size-1]

正常来说区间DP我们直接三重for循环遍历即可,但是由于n是5000,所以三重for循环是一定会超时的,我们不得不对其进行优化,我一开始以为可以用四边形不等式对其进行优化,突然发现这个代价函数是不满足单调性的,所以只能想其他的方法进行优化。

我们不妨讨论四个数的情况,以a[0],a[1],a[2],a[3],其最优情况只可能是以下两种之一,一种是直接对a[0],a[1]两个数进行操作和对a[2],a[3]两个数进行操作,或者是对a[0],a[3]利用代价y进行操作,再对a[1],a[2]两个数选择一个代价较小的方案进行操作,这是因为我们按照贪心策略如果使用代价为x的操作一定是选择两个距离比较小的位置进行操作,而代价为y的操作则无所谓,也就是说我们不可能选择a[0],a[2]两个数来进行代价为x的操作,所以我们就可以根据这个性质直接进行贪心,也就是代价为x的两个操作数中间是不可能有其他数的,所以我们可以在区间动态规划的过程中省掉枚举中间点的那一重循环,直接枚举三种情况即可,一种是对左边两个数操作,另一种是对右边两个数操作,还有一种就是对两端的数进行操作。这样复杂度就变为O(n^2)了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=5e3+10;
char a[N],b[N];
int c[N];
long long f[N][N];
int s[N][N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		long long n,x,y;
		scanf("%lld%lld%lld",&n,&x,&y);
		scanf("%s",a+1);
		scanf("%s",b+1);
		vector<int>p;
		for(int i=1;i<=n;i++)
			if(a[i]!=b[i]) p.push_back(i);
		if(x>=y)
		{
			if(p.size()&1) puts("-1");
			else if(p.size()==2)
			{
				if(p[0]+1==p[1])
				{
					if(n==2) printf("%lld\n",x);
					else printf("%lld\n",min(x,2*y));
				}
				else
					printf("%lld\n",y);
			}
			else
				printf("%lld\n",y*(p.size()/2));
		}
		else
		{
			if(p.size()&1)
			{
				puts("-1");
				continue;
			}
			for(int i=0;i<p.size();i++)
			for(int j=0;j<p.size();j++)
				f[i][j]=0x3f3f3f3f3f3f3f3f;
			for(int len=2;len<=p.size();len+=2)
			for(int l=0;l+len-1<p.size();l++)
			{
				int r=l+len-1;
				if(r-l==1)
				{
					if(p[r]-p[l]==1)
						f[l][r]=x;
					else
						f[l][r]=min((p[r]-p[l])*x,y);
				}
				else
				{
					f[l][r]=f[l+1][r-1]+min(y,(p[r]-p[l])*x);
					f[l][r]=min(f[l][r],f[l][l+1]+f[l+2][r]);
					f[l][r]=min(f[l][r],f[l][r-2]+f[r-1][r]);
				}
			}
			printf("%lld\n",f[0][p.size()-1]);
		}
	}
	return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值