Erik的新手训练营题解

Erik新手训练营第四次 题解

注:本人萌新,欢迎各位大佬在评论区指出我的错误和不足,也欢迎大家来讨论不同的题目解法。orz

A. Different Differences

题意
要求用1~n之间的整数构造一个大小为k的严格递增的数组A{a1,a2,a3…},然后根据数组A构造数组B,数组B中的元素为{a2-a1,a3-a2,…,an-an-1},使得B中不同的元素数目最多。
分析:因为B中的元素为A中元素的差,并且要求N中不同的元素最多,那么最小代价的构造就是B为{1,2,3,4,5,…},那么A中的元素是什么呢?
a2-a1=1;
a3-a2=2;

an-an-1=n-1;
我们不难推出
a1=1;
an=an-1 + n-1
但是要求我们取1~n中的数,这样构造会产生一个问题,an在等于n的时候,构造的数目不够k个怎么办?
这还不好办?我们在1~n的范围中,随便找几个没在A中的数加进去不久ok了
AC代码如下

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int k,n;
void slove(){
	vector<ll> nums;  //答案数组
	int mp[100];    //标记数组,用来记录这个数有没有在A中出现过
	memset(mp,0,sizeof(mp));
	int i;
	cin>>k>>n;
	ll sum=1;
	//构造A数组
	for(i=0;i<k;i++){
		if(sum+i<=n)sum+=i;  //注意要保证A中的元素不能越界
		else break;
		nums.push_back(sum);
		mp[sum]=1;			//标记该元素已经在A中了
	}
	//如果A中的元素不够k个,补齐,我这里是从后往前找的,其实怎么找都一样
	sum=n;
	while(i<k&&sum>0){
		if(!mp[sum]){
			nums.push_back(sum);
			i++; 
		}
		sum--;
	}
	//排序输出即可
	sort(nums.begin(),nums.end());
	for(int j=0;j<nums.size();j++)cout<<nums[j]<<" ";
	cout<<"\n";
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0); 
	int t;
	cin>>t;
	while(t--){
		slove();
	}
	return 0;
}

B. Binary Inversions

题意
给定一个长度为n的二进制数组,你可以进行至多一次操作,将一个位置的0变为1或者把1变成0. 定义了一个贡献:i,j(i,j表示数组下标)i<j&&nums[i]>nums[j],求个数。其实就是求(1,0)这个数组对的有多少对(1在前。0在后)。
分析:
现在问题就变成了我们怎么找(1,0)对 有多少个呢?
我们可以用贡献的思想。
假设我们处理到了第k个位置:
如果它为0,那么1~k-1之间有cnt1个1,那么这个0产生的贡献就是cnt;
而如果它是1呢?那我们就找k+1~n之间的0的个数,假设为cnt0,贡献就是cnt0。

统计求和得到一个ans(注意统计的时候统计1的贡献或者0的贡献,二选一,如果两个都统计的话会产生重复统计),也就是我们不做任何操作时有多少个(1,0)对。
有关重复的解释:
假设数组为10100
当我们统计到第一个1的时候,会有三个(1,0)对
到第一个0的时候,如果你还统计是不是就和统计第一个1中的情况重复了?

然后我们再来考虑进行操作后的数目:
在第k个位置,它为1或者为0的贡献我们都是能够算出来的,那么我们统计一下它们的差值,同时维护这个差值的最大值ret。
如果第k个位置是1:ret=max(ret,cnt0[k+1]-cnt1[k-1]),就是此位置为1的贡献 - 此位置为0的贡献
如果第k个位置为0:ret=max(ret,cnt1[k-1]-cnt0[k+1]),同理
其中cnt1[k]表示前k个位置中有多少个1,cnt0[k]表示k~n中有多少个0
那么ans+ret不就是答案嘛
AC代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
void slove(){
	int n;
	cin>>n;
	int cnt0[maxn],cnt1[maxn]; //cnt1统计前k个位置1的个数,cnt0统计k~n中0的个数
	memset(cnt0,0,sizeof(cnt0));
	memset(cnt1,0,sizeof(cnt1));
	short nums[maxn];
	//读入数据并统计cnt1
	for(int i=1;i<=n;i++){
		cin>>nums[i];
		if(nums[i]==1)cnt1[i]=cnt1[i-1]+1;
		else cnt1[i]=cnt1[i-1];
	}
	//统计cnt0
	for(int i=n;i>=1;i--){
		if(nums[i]==0)cnt0[i]=cnt0[i+1]+1;
		else cnt0[i]=cnt0[i+1];
	} 
	//计算总和ans的同时,维护差值ret。
	//这里我计算的是0的贡献(即当前位置为0时,它前面有多少个1)
	ll ans=0;
	int ret=0;
	for(int i=1;i<=n;i++){
		if(nums[i]==0){
			ans+=cnt1[i-1];
			//cnt0[i+1]-cnt1[i-1]表示如果把这变成1,会多出的(1,0)对的对数
			ret=max(ret,cnt0[i+1]-cnt1[i-1]);
		}else{
			ret=max(ret,cnt1[i-1]-cnt0[i+1]);
		}
	}
	cout<<ans+ret<<endl;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0); 
	int t;
	cin>>t;
	while(t--){
		slove();
	}
	return 0;
}

C. awoo’s Favorite Problem

题意
给你两个长度为n的仅有‘a’ ‘b’ ‘c’组成的字符串S和T
你可以进行任意次的下面的操作:
把"ab"变成"ba"
把"bc"变成"cb"(其实意思就是把a移动到b后面或者把c移动到b前面)
为你S经过任意次操作能不能变成T
分析
经过分析,我们发现a只能往后移动,c只能往前移动,同时“ac”和“ca”之间不能相互转换,也就是说a和c的相对位置是不会变化的,换句话说,如果S经过变化后能变成T,那么必然有两个数组把’b’都去掉后,剩余的部分相同。举个例子S=“abbabc” T=“bbaacb”,都去掉’b’后我们发现它们都变成了"aac"
还有一点,注意a只能往后移动,c只能往前移动
S=“ba" T="ab"这种情况,S同样不能经过变化变成T
那么就好说了:
如果对某个位置以下情况同时都不成立,S就不能变成T
1: S中S[i]!=T[j]
2: S[i]=‘a’&&i<j
3: S[i]=‘c’&&i>j
最后再判断一下’b’的个数来确定S和T中’a’‘b’'c’中的个数相不相等即可
AC代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
void slove(){
	string s,t;
	int n;
	cin>>n;
	cin>>s>>t;
	int ss=0,ts=0;//统计s和t中'b'的个数
	int i=0,j=0;
	for(i=0;i<n;i++){
	//如果t[j]=='b',接着往后走,并统计进ts
		while(j<n&&t[j]=='b'){
			ts++;
			j++;
		}
	//如果s[i]=='b',统计进ss
		if(s[i]=='b'){
			ss++;
			continue;
		}
	//如果三种情况都不满足,那么s肯定不能变成t
		if(s[i]!=t[j]||s[i]=='a'&&i>j||s[i]=='c'&&i<j){
			cout<<"NO"<<endl;
			return;
		}
		j++;
	}
	//统计t中剩余的'b'
	while(j<n&&t[j]=='b'){
		ts++;
		j++;
	}
	//判断'b'的个数相不相等
	if(ts!=ss)cout<<"NO"<<endl;
	else cout<<"YES"<<endl;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0); 
	int t;
	cin>>t;
	while(t--){
		slove();
	}
	return 0;
}

D. Rock and Lever

题意
给定一个长度为n的由非负整数组成的数组nums,求点对(i,j),使得ai&aj>=ai^aj的个数
分析
我们先来看一下运算符
&: 1 & 0=0; 0 & 1=0; 0 & 0=0; 1 & 1=1
^: 1 ^ 0=1;0 ^ 1=1; 0 ^ 0 =0; 1 ^ 1=0
再想一下我们平时比较两个数的大小的时候是怎么做的?
是不是从最高位看起?
假设我们要比的数是28和19
我们看到2比1大的时候比较就已经结束了,那么二进制同样是这样
那么我们假设ai的二进制最高位为k1,aj的二进制最高位为k2
当k1==k2的时候,肯定有ai&aj>ai^aj,因为此时最高位的&结果为1, ^结果为 0
当k1!=k2的时候,肯定有ai&aj<ai^aj, 因为此时 最高位 ^ 的结果为1,&的结果为0
那么问题就变成了有多少对(i,j)使得它们的二进制最高位相等
假设nums中二进制最高位为2的一共有n个,那么它一共能产生的点对数目就是n*(n-1)/2,等差数列求和嘛
AC代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
//获取二进制最高位
int getVal(int x){
	for(int i=31;i>=0;i--){
		if(x&(1<<i)){
			return i;	
		}
	}
	return 0;
}
void slove(){
	int nums[maxn];
	int cnt[100];
	memset(cnt,0,sizeof(cnt));
	int n;
	cin>>n;
	//读入数据,并存入每个数的二进制最高位的位置
	for(int i=1;i<=n;i++){
		int k;
		cin>>k;
		nums[i]=getVal(k);
		cnt[nums[i]]++;
	}
	//统计点对,求和
	ll ans=0;
	for(int i=0;i<100;i++){
		ans+=1ll*cnt[i]*(cnt[i]-1)/2;
	}
	//输出即可
	cout<<ans<<endl;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0); 
	int t;
	cin>>t;
	while(t--){
		slove();
	}
	return 0;
}

E. Two Arrays

题意
给你一个大小为n的数组A和一个目标数t,要求你把它分成两个数组B和C,使得B中点对(i,j)(满足B[i]+B[j]=t)的个数f(B)+C中的点对f©个数最少
分析
其实很简单,比如t=7,
A={1,2,3,4,5,6}
如果1分到B中,那么7-1=6就肯定分到C中呗;2分到B中,7-2=5就分到C中呗
你问我如果x分到了B中,但A中没有t-x怎么办?那就不用管它
你又要问了,如果x==t-x怎么办呢?好说:尽可能的等分它就好了
比如A={3,3,3,3}
将它分成B={3,3},C={3,3}肯定比分成B={3},C={3,3,3}更优。
具体计算证明交给读者,这里不再展示
分析完毕,我们直接上代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
void slove(){
	/*cnt用来统计某个数出现的次数,mp0表示这个数有几个被分到了B数组中,mp1表示这个数有几个
	被分到了C数组中*/
	unordered_map<int,int> cnt,mp0,mp1;
	int nums[maxn],n,t;
	//读入统计个数
	cin>>n>>t;
	for(int i=1;i<=n;i++){
		cin>>nums[i];
		cnt[nums[i]]++;
	}

	for(int i=1;i<=n;i++){
		//如果这个数已经被分配过了,不在考虑
		if(mp0[nums[i]]||mp1[nums[i]])continue;
		//如果t-nums[i]在数组A中
		if(cnt[t-nums[i]]){
			//nums[i]=t-nums[i]的情况,尽可能等分即可
			if(nums[i]+nums[i]==t){
				mp0[nums[i]]=(cnt[nums[i]]+1)/2;//这里是为了处理该数有奇数个的情况
				mp1[nums[i]]=cnt[nums[i]]/2;
			}else{
			//nums[i]!=t-nums[i],此时一个分到B,另一个分到C即可
				mp0[nums[i]]=cnt[nums[i]];
				mp1[t-nums[i]]=cnt[t-nums[i]];
			}
		} 
	}
	//输出即可
	for(int i=1;i<=n;i++){
		if(mp0[nums[i]]){
			cout<<"0 ";
			mp0[nums[i]]--;
		}else if(mp1[nums[i]]){
			cout<<"1 ";
			mp1[nums[i]]--;
		}else cout<<"0 ";
	}
	cout<<"\n";
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0); 
	int t;
	cin>>t;
	while(t--){
		slove();
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值