VP记录:Educational Codeforces Round 148 (Rated for Div. 2) A~D1

文章包含三道算法竞赛题目,主要涉及字符串处理、数组操作和序列优化。A题是关于回文串的模拟,关键在于奇数次出现的字符;B题要求在限制操作次数下最大化数组之和,采用排序和枚举策略;C题是构造保持相邻差不变的子序列,通过删除单调序列中间元素实现。D题(简化版)讨论了一种操作模式下的数组变化,利用贪心思想和二分查找确定最大可能的数组值。
摘要由CSDN通过智能技术生成

传送门:CF

前题提要:本人临近期中,时间较紧,且关于D2暂时没有想到优化算法,因此准备留着以后有时间再继续解决

A题:A. New Palindrome

简单的模拟题,考虑记录每一个字母出现的次数.很容易发现奇数次的数字只能出现一次.因为最多只能在正中间放一个.并且因为不能和初始字符相同,因此我们出现此处大于等于2的字符至少应该出现两个(这样可以保证交换).

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int main() {
	int T=read();
	while(T--) {
		map<char,int>mp;
		string s;cin>>s;
		for(int i=0;i<s.length();i++) mp[s[i]]++;
		int cnt1=0,cnt2=0;int sum=0;
		for(auto it : mp) {
			if(it.second&1) {
				cnt1++;
				sum+=it.second;
			}
			else cnt2++;
		}
		if(cnt1>1) cout<<"NO"<<endl;
		else {
			if((cnt2==1&&sum<=1)||cnt2==0) cout<<"NO"<<endl;
			else cout<<"YES"<<endl;
		}
	}
	return 0;
}

B题:Maximum Sum

不难发现我们只有两种操作,并且有一个很重要的性质就是两个操作的操作次数总和必须等于 K K K.所以我们不妨枚举第一种操作的操作次数.这样的话,我们就知道两种操作分别操作了多少次了.我们还发现 2 ∗ k < n 2*k<n 2k<n,这就保证了我们删除数字的时候是不会发生交叉的.
考虑将数组进行一个排序.然后枚举两种操作的次数,对于所有情况取一个 m a x max max即可.
作为B题,本题思维难度较低

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
#define int long long
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];int Sum[maxn];
signed main() {
	int T=read();
	while(T--) {
		int n=read();int k=read();
		int sum=0;
		for(int i=1;i<=n;i++) {
			a[i]=read();
			sum+=a[i];
		}
		sort(a+1,a+n+1);
		for(int i=1;i<=n;i++) Sum[i]=Sum[i-1]+a[i];
		int maxx=-ll_INF;
		for(int i=0;i<=k;i++) {
			int num=k-i;
			maxx=max(maxx,sum-(Sum[i*2])-(Sum[n]-Sum[n-num]));
		}
		cout<<maxx<<endl;
	}
	return 0;
}

C题:C. Contrast Value

一道简单思维题.发现我们需要构造一个序列满足相邻差的和与原序列一样.
因为是子序列,我们不妨考虑在原序列中进行删数,这样最终留下来的数字就是原来的序列的子序列了.
因为要满足相邻差不变.所以我们想一下什么情况下删除一个数会不改变相邻的差的总和呢.诶,我们随便试一试就会发现应该是一个单调序列除首尾以外的中间数字.这个看起来十分显然并且也不难证明,所以此处就不在提供证明了.
当你想到这一点之后本题也就不难解决了.只要考虑保留一段单调序列的首尾数字即可.
但是有一个小细节需要注意,也就是当我们相邻的两个数相等时,此时既可以看成单调增序列,也可以看成单调减序列,此时需要单独考虑,这样可以避免影响之后的单调性判断

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];
int main() {
	int T=read();
	while(T--) {
		int n=read();
		for(int i=1;i<=n;i++) {
			a[i]=read();
		}
		if(n==1) {
			cout<<1<<endl;
			continue;
		}
		if(n==2) {//两个数的时候是需要特判断的,因为这两个数可能相等也可能不同.
			if(a[2]==a[1]) {
				cout<<1<<endl;
				continue;
			}
			else {
				cout<<2<<endl;
				continue;
			}
		}
		int flag=a[2]>=a[1]?1:-1;
		if(a[2]==a[1]) flag=0;
		vector<int>ans;ans.push_back(a[1]);
		for(int i=3;i<=n;i++) {
			if(flag==1) {
				if(a[i]>=a[i-1]) continue;
				else {
					ans.push_back(a[i-1]);
					flag=-1;
				}
			}
			else if(flag==-1){
				if(a[i]>a[i-1]) {
					ans.push_back(a[i-1]);
					flag=1;
				}
				else continue;
			}
			else {
				if(a[i]>a[i-1]) flag=1;
				else if(a[i]<a[i-1]) flag=-1;
				else flag=0;
			}
		}
		cout<<ans.size()+(a[n]==ans.back()?0:1)<<endl;//注意最后的一个数字肯定是属于一段序列的尾部!!!
	}
	return 0;
}

D1:Red-Blue Operations (Easy Version)

首先读完题目不难发现有一个贪心想法:那就是尽量的让这个数组中的最小的那一个数字增加(此处的最小是指每一次操作后的最小).
所以不妨先对原数组进行一个排序.
诶,然后我们把玩一下题意,会发现一个简单的小结论.对于任意一位上的数字来说,这个数字最大的增加幅度只可能是最后一次操作.因为对于这个数字来说,我们每一次都是先增后减.并且减的肯定比增的要多.所以此时增加幅度要么是第一次对该数字进行的操作,要么是经过加减循环之后最后一次的加的操作.我们发现如果是后者的话,显然不如直接加最后一次操作来的更为优秀.诶,所以此时我们就有了一个结论,对于一个位置上的数字来说,我们想要最大化的增加他,就加一个最大的数字即可
然后对于本题,我们考虑使用二分答案,先来简单的证明一下二分的单调性.

我们二分最终的符合的数字 m i d mid mid.我们先来想一下对于一个数列来说,如何才是最佳的改变方法.利用之前叙述的小结论.我们贪心的去想,对于小于 m i d mid mid的所有数字,我们都给它加上最大的操作,此时这个数字变的肯定是最大的.那么假设我们操作完所有小于 m i d mid mid的数字,此时我们肯定是剩下来一些操作没有完成(当然也可能没有,此处分析有剩下来的情况).因为我们想要使用之前的小结论,此时我们就需要保证剩下来的所有操作都是在上边的最大操作之前完成的.
我们此时有几种情况能解决剩下来的操作:
1.我们考虑将剩下来的操作给大于 m i d mid mid的数字,我们发现对于任意的一个大于 m i d mid mid的数字,我们可以每一次承担2次操作然后换来一次减一(至于为什么是连续的两次操作,证明并不难,因为篇幅原因请自行理解)
2.我们考虑将剩下来的操作给之前小于mid但是现在大于mid的数字,我们发现对于那些数字,我们也可以承担2次操作然后换来一次减一
3.我们先进行上述的两种必然可行的方案.假设我们此时还剩下一些操作.那么我们再进行第三种操作.此时我们的情况是所有数组中的数字都是不能再承担两次操作了.那么此时我们会发现对于大于 m i d mid mid的数字来说,我们可以花费奇数次操作来发生正贡献(加减加,依旧是加).并且不单单是这一点,只要我们有两个大于mid的数字,对于任意奇偶的需要操作数,我们都可以完成.因为对于偶数,我们可以拆成两个奇数
4.还需要注意的是,当所有的数列中的数字都大于当前的 m i d mid mid时,此时我们需要特判,因为此时只要我们有两个数,那么我们就可以采取第三种方案来满足题意.如果只有一个数,我们需要判断操作数的奇偶性

基于上述的几种改变方法,我们此时来考虑二分的单调性.
1.假设当前的 m i d mid mid满足题意,为什么不需要考虑小于 m i d mid mid?
显然当前的 m i d mid mid满足题意,我们只需要考虑大于 m i d mid mid的答案是否存在即可,因为我们最终是取 m a x max max
2.假设当前的 m i d mid mid不满足题意,为什么不需要考虑大于 m i d mid mid
我们发现当前 m i d mid mid不满足题意,这意味的当前我们无法分配操作数.假设我们此时遇到了一个更大的 m i d 2 mid2 mid2,设原本的分界点为 p o s pos pos(也就是 a [ p o s ] a[pos] a[pos]恰好大于等于mid).我们此时需要将 a [ p o s ] a[pos] a[pos]也变得大于 m i d 2 mid2 mid2.但是试想一下,我们之前已经无法做到保证所有的数字大于 m i d mid mid的了.根据贪心的想法,最后的操作必然是加减加的最后一次加.那么之前这样都不满足题意,此时我们需要拿几次"减加"的操作来放到 a [ p o s ] a[pos] a[pos]上,这必然会导致其他位置的数字减少,就更加不能满足题意了.

可能讲的有点抽象,建议结合代码理解一下QAQ,本题虽讲解篇幅较长,但是作为D1,其思维难度并不高

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
#define int long long
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];int n,q;
int check(int mid,int k) {
	int temp=k;int pos=n+1;
	vector<int>b;b.push_back(0);
	for(int i=1;i<=n;i++) b.push_back(a[i]);
	//因为a数组加了之后会改变单调性,所以b数组用来重新排序
	for(int i=1;i<=n;i++) {
		if(b[i]<mid) {//给每一个小于mid的数字加上当前可行的最大的操作
			if(b[i]+temp<mid) return false;
			b[i]+=temp;
			temp--;
		}	
		else {
			pos=i;break;
		}
	}
	int cnt=0;
	for(int i=pos;i<=n;i++) {//先进行操作1
		cnt+=b[i]-mid;
	}
	temp-=cnt*2;
	if(temp<=0) return true;  
	if(pos==1) {
		if(temp&1) return true;
		else {
			if(n>=2) return true;
			else return false;
		}
	}
	sort(b.begin()+1,b.begin()+pos-1);
	for(int i=pos-1;i>=1;i--) {//再进行操作2
		int num=(b[i]-mid)*2;
		if(num<=0) continue;
		if(temp<=num) {
			if(temp&1) {
				temp=1;break;
			}
			else {
				temp=0;break;
			}
		}
		else {
			temp-=(b[i]-mid)*2;
		}
	}
	if(pos<=n) {//考虑操作3
		if((n-pos+1)!=1) return true;
		else if(temp&1) return true;
	}
	if(temp>0) return false;
	else return true;
}
signed main() {
	n=read(),q=read();
	for(int i=1;i<=n;i++) {
		a[i]=read();
	}
	sort(a+1,a+n+1);
	for(int i=1;i<=q;i++) {
		int k=read();
		int l=-1e10,r=1e10;
		int ans=0;
		while(l<=r) {
			int mid=(l+r)/2;
			if(check(mid,k)) {
				l=mid+1;
				ans=mid;
			}
			else {
				r=mid-1;
			}
		}
		cout<<ans<<" ";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值