Codeforces Round 963 div2 A-D

好久没更新了,最近天天打杭电和牛客,题都没时间补了写题解就更没时间了

比赛链接

A

题意:

在这里插入图片描述

做法:每个选项最多答对 n n n道,如果出现的某个选项小于 n n n项,那么该选项最多答对数量应该是这些全对了,如果某个选项大于等于 n n n项,那么该选项最多答对数量应该是 n n n。把 A B C D ABCD ABCD四个选项的答案加起来就行。

c o d e : code: code:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
const int N = 1;
int t;
int a[N];
string s;
int n,m,h,w;
int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		cin>>s;
		int numa=0;int numb=0;int numc=0;int numd=0;
		int num1=0;int num2=0;int ans=0;
		for(int i=0;i<s.size();i++)
		{
			if(s[i]=='A') numa++;
			if(s[i]=='B') numb++;
			if(s[i]=='C') numc++;
			if(s[i]=='D') numd++;
		}
		if(numa<=n)
		{
			ans+=numa;
		}
		else ans+=n;
		if(numb<=n)
		{
			ans+=numb;
		}
		else ans+=n;
		if(numc<=n)
		{
			ans+=numc;
		}
		else ans+=n;
		if(numd<=n)
		{
			ans+=numd;
		}
		else ans+=n;
		printf("%d\n",ans);
	}
	return 0;
}

B

题意:

在这里插入图片描述

数据范围: N < = 2 ∗ 1 0 5 N<=2*10^5 N<=2105

做法:
先特判序列是否已经全为奇数或全为偶数。然后考虑有奇数也有偶数怎么做。
首先,两个奇数相加或者两个偶数相加奇偶性不变,只有奇数+偶数=奇数。
所以最后一定是把所有偶数变成了奇数,发现操作次数的下限就是偶数的个数。那么,如果序列种最大的数是奇数,则只需要让每个偶数跟这个最大的数操作一次,所有的偶数就可以变成奇数,答案为偶数的个数。
如果最大的数是偶数 A i A_i Ai,不妨把数列先排序,我们先尽可能多地把偶数一次变成奇数。假设当前最大奇数是 c n t cnt cnt,目前序列仍然存在的最小偶数为 A i A_i Ai,那么如果 c n t > A i cnt > A_i cnt>Ai,就把 A i A_i Ai变成 c n t + A i cnt+A_i cnt+Ai,此时 c n t cnt cnt变成 c n t + A i cnt+A_i cnt+Ai。如果 c n t < A i cnt<A_i cnt<Ai,我们可以拿最大的偶数 m a x o d d maxodd maxodd c n t cnt cnt操作一次,把 c n t cnt cnt变成 c n t + m a x o d d cnt+maxodd cnt+maxodd,再拿更新后的 c n t cnt cnt去跟剩余所有偶数去操作,此时 c n t > m a x o d d > = 任意偶数 cnt>maxodd>=任意偶数 cnt>maxodd>=任意偶数,此情况即为最优解。我们在下限的基础上只多用了一次操作让最大奇数变成最大数。

c o d e : code: code:赛时写的有点丑陋

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#define int long long
using namespace std;
const int N = 2e5+5;
int t;
int a[N];
int vis[N];
int n,m,h,w;
signed main()
{
	cin>>t;
	while(t--)
	{
		scanf("%lld",&n);
		int flag1=0;int flag2=0;
		int maxn=0;
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		for(int i=1;i<=n;i++)
		{
			if(a[i]%2) flag1++;
			if(a[i]%2==0) flag2++;
			maxn=max(maxn,a[i]);
			vis[i]=0;
		}//1 ji
		if(flag1==0||flag2==0)
		{
			printf("0\n");
			continue ;
		}//特判全奇数/全偶数
		sort(a+1,a+1+n);
		int maxji=0;
		for(int i=n;i>=1;i--)
		{
			if(a[i]%2)
			{
				maxji=a[i];//最大奇数
				break;
			}
		}
		int ans=0;
		int fflag=0;
		for(int i=1;i<=n;i++)
		{
			if(a[i]%2==0&&a[i]<maxji)
			{
				ans++;
				maxji+=a[i];//注意最大奇数也要更新
				vis[i]=1;
				continue ;
			}
			if(a[i]%2==0&&a[i]>maxji)
			{
				fflag=1;
				break;
			}
		}
		if(!fflag)//所有偶数都更新过了
		{
			printf("%lld\n",ans);
			continue ;
		}
		int maxou=0;
		for(int i=n;i>=1;i--) 
			if(a[i]%2==0)
			{
				maxou=a[i];
				vis[i]=1;
				break;
			}
		maxji+=2*maxou;
		ans+=2;
		for(int i=1;i<=n;i++)
		{
			if(a[i]%2==0&&vis[i]==0)
			{
				ans++;
				maxji+=a[i];
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

C

题意:

在这里插入图片描述

数据范围: N , K < = 2 ∗ 1 0 5 N,K<=2*10^5 N,K<=2105

做法:
发现如果存在合法情况,那么答案一定出现在 [ M A X ( A i ) , M A X ( A i ) + K ] [MAX(A_i),MAX(A_i)+K] [MAX(Ai),MAX(Ai)+K]。又发现所有灯都是 k k k分钟亮 k k k分钟暗,所以在答案区间内每个灯都是前 L i L_i Li分钟亮灯后 K − L i K-L_i KLi分钟暗灯或者前 L i L_i Li分钟暗灯后 K − L i K-L_i KLi分钟亮灯。我们只需要看每个 A i A_i Ai在该区间起点是亮还是暗,还能亮/暗多久。对所有以亮灯开始的长度取最短亮灯时间,对所有暗灯开始的长度取最长暗灯时间,如果最短亮灯时间小于最长暗灯时间,那么操作就不合法,因为最长暗灯时间的那个灯与最短亮灯时间的那个灯在答案区间内至多有一个亮着。
如果操作合法,则最早全部亮灯时刻应该是最长暗灯长度+答案区间起点+1
虚线框就是维护的最长暗灯长度和最短亮灯长度
如图,虚线框就是维护的最长暗灯长度和最短亮灯长度

c o d e : code: code:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#define int long long
using namespace std;
const int N = 2e5+5;
int t;
int a[N];
int vis[N];
int lit[N];
int n,m,h,w;
signed main()
{
	cin>>t;
	while(t--)
	{
		int k;
		scanf("%lld%lld",&n,&k);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),vis[i]=0,lit[i]=0;
		sort(a+1,a+1+n);
		int flag=0;
		int minl=0x3f3f3f3f;int maxd=0;
		for(int i=1;i<=n;i++)
		{
			int cnt=(a[n]-a[i])/k;
			int pos=a[i]+k*cnt;
			if(cnt%2==1)
			{
				vis[i]=0;//an 
				lit[i]=pos+k-a[n];
				maxd=max(maxd,lit[i]);
			}
			else
			{
				vis[i]=1;//liang 
				lit[i]=pos+k-a[n];
				minl=min(minl,lit[i]);
			}
			if(minl<=maxd) flag=1;
		}
		if(flag)
		{
			printf("-1\n");
			continue ;
		}
		else
		{
			if(maxd==0)
			{
				printf("%d\n",a[n]);
			}
			else
			{
				printf("%d\n",a[n]+maxd);
			}
		}
	}
	return 0;
}

D

题意:
在这里插入图片描述

数据范围: N , K < = 1 0 5 N,K<=10^5 N,K<=105

做法:首先,如果我们按模 k k k把下标分组,序列下标为0,1,2…k,0,1,2,…k…我们任意删去 k k k个数,该序列还是0,1,2…k,0,1,2,…k…,所以如果最终剩下 m m m个数,那他们的下标连起来一定是0,1,2,…m-1。
发现 A i > = c n t 的 A i 的数量 A_i>=cnt的A_i的数量 Ai>=cntAi的数量关于 c n t cnt cnt单调,所以我们可以二分最终序列的中位数。假设目前二分的答案为 c n t cnt cnt
d p [ i ] dp[i] dp[i]为选到前 i i i个数时 > = c n t >=cnt >=cnt的数的个数,由于最终序列下标连续,所以 d p [ i ] dp[i] dp[i]要么是 d p [ i − k ] dp[i-k] dp[ik]转移过来,要么是 d p [ i − 1 ] dp[i-1] dp[i1]转移过来,特别注意由 d p [ i − k ] dp[i-k] dp[ik]转移过来时不能再选 A i A_i Ai了,因为 A i A_i Ai A i − k A_i-k Aik的下标均为 ( i m o d k ) (i mod k) (imodk)。而由 i − 1 i-1 i1转移过来是可以选 A i A_i Ai的。那么 d p [ i ] = m a x ( d p [ i − k ] , d p [ i − 1 ] + ( A i > = c n t ) ) dp[i]=max(dp[i-k],dp[i-1]+(A_i >=cnt )) dp[i]=max(dp[ik],dp[i1]+(Ai>=cnt))

又要注意如果 i m o d k = = 0 imodk==0 imodk==0,则 d p [ i ] dp[i] dp[i]只能从 d p [ i − k ] dp[i-k] dp[ik]转移过来。答案序列也有可能是从 A i A_i Ai开始的,那么 d p [ i ] = m a x ( ( A i > = c n t , d p [ i − k ] ) ) dp[i]=max((A_i >=cnt,dp[i-k])) dp[i]=max((Ai>=cnt,dp[ik]))

设最终剩余序列长度为 L e n t h Lenth Lenth,那么 d p [ n ] > = L e n t h / 2 + 1 dp[n]>=Lenth/2+1 dp[n]>=Lenth/2+1时,说明答案合法
c o d e : code: code:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
const int N = 5e5+5;
int t;
int a[N];
int n,m,k,w;
int dp[N];
bool check(int x)
{
	for(int i=0;i<=n;i++) dp[i]=0;
	for(int i=1;i<=k;i++)
	{
		dp[i]=dp[i-1];
		if(a[i]>=x) dp[i]++;
	}
	for(int i=k+1;i<=n;i++)
	{
		if(i%k==1)
		{
			if(a[i]>=x) dp[i]=1;
			dp[i]=max(dp[i-k],dp[i]);
		}
		else
		{
			dp[i]=dp[i-1];
			if(a[i]>=x) dp[i]++;
			dp[i]=max(dp[i],dp[i-k]);
		}
	}
//	for(int i=1;i<=n;i++) printf("i = %d dp = %d\n",i,dp[i]);
	int maxnum=0;
//	for(int i=1;i<=n;i++) maxnum=max(maxnum,dp[i]);
	int cnt=(n-n/k*k);
	if(cnt==0) cnt=k;
	if(dp[n]>=(cnt)/2+1) return 1;
	else return 0;
}
int main()
{
	cin>>t;
	while(t--)
	{
		scanf("%d%d",&n,&k);
		int r=0;
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),r=max(r,a[i]);
		if(n<=k)
		{
			sort(a+1,a+1+n);
			printf("%d\n",a[(n+1)/2]);
			continue ;
		}
		int l=1;
		int ans=0;
//		printf("tot = %d\n",(n-(n/k)*k)/2+1);
		while(l<=r)
		{
			int mid=(l+r)>>1;
//			printf("l = %d r = %d mid = %d\n",l,r,mid);
			if(check(mid))
			{
				ans=mid;
				l=mid+1;
			}
			else r=mid-1;
		}
		printf("%d\n",r);
	}
	return 0;
}
  • 11
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值