Codeforces Round 963 (Div. 2)

前言

        看完小胖夺冠就来打打比赛,可能出题人也没想到有几千人卡在了 D 题,比赛时想到了二分想到了用 dp 想到了性质但是可惜卡在了状态转移。

        Standings:1497

        题目链接:Dashboard - Codeforces Round 963 (Div. 2) - Codeforces

A. Question Marks

        题意:

        一张试卷有 4n 个题目,每个选项 "A" "B" "C" "D" 都是其中 n 个题目的答案。给出 Tim 的考场答案,问最多能答对多少题。

        思路:

        贪心,尽可能让 Tim 所写的答案就是标准答案,但是每个选项最多只能计 n 个。

#include<cstdio>
#include<cstring>
using namespace std;

int T,n,a[5],ans;
char s[10005];

int min(int x,int y) { return x < y ? x : y ; }

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		memset(a,0,sizeof a),ans = 0;
		scanf("%d",&n);
		scanf("%s",s + 1);
		for (int i = 1;i <= n * 4;++ i)
			if(s[i] >= 'A' && s[i] <= 'D') ++ a[s[i] - 'A'];
		for (int i = 0;i < 4;++ i) ans += min(a[i],n);
		printf("%d\n",ans);
	}
	return 0;
}

B. Parity and Sum

        题意:

        给一个长度为 n 的序列 a ,每次可以选择两个奇偶性不同的数,并让较小的那个数变成这两个数之和,问使得序列中所有数奇偶性相同时最小的操作数。

        思路:

        特判一下原序列就合法的情况,剩下的情况显然我们只能把所有数都变成奇数。

        贪心,每次用最大的那个奇数和某个比他小的偶数操作,然后更新奇数的最大值,这样每次改变奇偶性需要的操作数是 1 且是最小的。上一步完成后如果目前最大的奇数仍然小于一些偶数,那就随便用一个奇数和最大的那个偶数操作两次,会得到一个大于所有偶数的奇数,接下来把剩下的偶数都操作一下即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define N 200005

int T,n;
long long ans,a[N];

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d",&n),ans = 0ll;
		for (int i = 1;i <= n;++ i) scanf("%lld",&a[i]);
		sort(a + 1,a + n + 1);
		long long mx = -1ll;
		for (int i = n; i ;-- i)
			if(a[i] & 1)
			{
				mx = a[i];
				break;
			}
		if(mx == -1ll)
		{
			printf("0\n");
			continue;
		}
		int flag = 0;
		for (int i = 1;i <= n;++ i)
			if(!(a[i] & 1))
			{
				if(flag)
				{
					++ ans;
					continue;
				}
				if(mx > a[i])
				{
					mx += a[i];
					++ ans;
				}
				else
				{
					flag = 1;
					ans += 2ll;
				}
			}
		printf("%lld\n",ans);
	}
	return 0;
}

C. Light Switches

        题意:

        公寓里有 n 盏灯,每盏灯有个安装时间 a_i ,在安装那一刻起 k 分钟内灯亮,k 分钟后起的又一个 k 分钟内灯灭,交替进行。即在 a_i ,a_i + k ,a_i + 2k ,a_i + 3k ...... 时刻灯的状态会发生改变,问最早在哪一刻所有的灯都发亮。

        思路:

        所有灯的周期都是 k 分钟,我们可以从这里入手。也就是说在最后一盏灯安装之后,每隔 2k 分钟所有灯的状态集合都是一样的。于是我们只需考虑在最后那盏灯安装后的 2k 分钟里灯的状态。容易求出每盏灯在这个时间内亮灯的区间,求一下交集即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define N 200005

int T,n,k,l,r,flag,a[N];

int cross(int x,int y)
{
	if(x > r || y < l) return 0;
	if(x > l) l = x;
	if(y < r) r = y;
	return 1;
}

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d%d",&n,&k);
		for (int i = 1;i <= n;++ i) scanf("%d",&a[i]);
		sort(a + 1,a + n + 1);
		l = 0,r = k - 1,flag = 1;
		for (int i = 1,p,q;i <= n;++ i)
		{
			int now = a[n] - a[i];
			if((now / k) & 1)
			{
				now = now % k;
				p = k - now;
				q = k - now + k - 1;
			}
			else
			{
				now = now % k;
				p = 0;
				q = k - now - 1;
			}
			flag = cross(p,q);
			if(!flag) break;
		}
		if(!flag) printf("-1\n");
		else printf("%d\n",a[n] + l);
	}
	return 0;
}

D. Med-imize

        题意:

        给一个长度为 n 的序列 a ,你需要进行若干次操作,每次删掉序列中连续的 k 个数,直到序列的长度小于等于 k 。求最后剩下的序列中,中位数的最大值(这里中位数定义为长度为 n 的序列从小到大排序后第 \lfloor \frac{n + 1}{2} \rfloor 大的数)。

        思路:

        首先肯定不能用排序求中位数,这样太慢。

        我们可以二分中位数的值,判断剩下的序列中大于等于这个值的数最多有多少个,如果超过了一半就说明这个值是合法的。根据题意,剩下序列的长度是确定的:m = (n - 1) % k + 1 ,简单观察可知题目实际上是要挖掉若干段不相交的长度为 k 的区间。那么剩下数列中相邻的两个数要么在原数列中就是相邻的,要么在原数列的下标相差若干个 k 。

        依次可以得到一条关键线索:原数列中下标为 i 的数(下标从 0 开始)要是留下来,它在最终数列中的下标一定是 i % k 。这样我们就可以 dp 了,设 f_i 表示最终数列中 0 ~ i 位可以产生大于 x 的数的最多个数。

        时间复杂度:O(n log(max - min))

#include<cstdio>
#include<cstring>
using namespace std;

#define N 500005

int T,n,k,m,a[N],f[N],ans;

int min(int x,int y) { return x < y ? x : y ; }

int max(int x,int y) { return x > y ? x : y ; }

int check(int x)
{
	for (int i = 0;i < m;++ i) f[i] = 0;
	for (int i = 0;i < n;++ i)
		f[i % k] = max(f[i % k],(i % k) ? (f[i % k - 1] + (a[i] >= x)) : (a[i] >= x));
	return (f[m - 1] >= m - (m + 1) / 2 + 1);
}

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d%d",&n,&k),m = (n - 1) % k + 1,ans = 0;
		int l,r;
		l = 1e9,r = 1;
		for (int i = 0;i < n;++ i) scanf("%d",&a[i]),l = min(l,a[i]),r = max(r,a[i]);
		while (l <= r)
		{
			int mid = (l + r) >> 1;
			if(check(mid)) l = mid + 1,ans = mid;
			else r = mid - 1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

后面的题待补。

总结

        这场 D 题有点 “万事俱备只欠东风” 的感觉,明明好像已经把整道题想明白了,甚至知道考查的算法,但就是有一点关键线索没反应过来,可能是题目做少了。发现最近比赛中很多有关取模同余的题,想到取模就能做出来,没想到就做不了,也算是一个经验总结吧。感觉最近刷了太多思维题,要补补算法了。

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值