Codeforces Round #616(Div. 2) (前三题题解)

Codeforces Round #616(Div. 2) (前三题题解)


A. Even But Not Even

题目大意是给你一个长度不超过3000的数字串,让你删减任意个数字之后,使得剩下的数字组成的数满足:①是奇数,②所有数字的和为偶数,③没有前导零。实际上只要找到两个奇数就可以了,没有说明不行。时间复杂度为O(n)。

AC代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cmath>
using namespace std;

#define maxn 3005

int t, n;

int main()
{
	int cnt = 0, t1 = 0, t2 = 0, tmp = 0;
	scanf("%d", &t);// 输入测试点数量
	while (t--)
	{
		cnt = 0;// cnt记录奇数的数量,每一轮初始化为0
		scanf("%d", &n);// 每一轮数字的数量
		for (int i = 0; i < n; i++)
		{
			scanf("%1d", &tmp); // 一位一位读入数字,省去数组的空间(虽然空间省来没什么用)
			if (tmp & 1) // 如果是奇数
			{
				cnt++;
                //其实选择两个相对顺序不变的奇数就可以了,我这样处理是取最后两个奇数,同时保证相对顺序不变,有点像移位
				t2 = t1; 
				t1 = tmp;
			}
		}
		if (cnt > 1)	printf("%d%d\n", t2, t1); // 有两个及以上的奇数说明可行
		else			printf("-1\n"); // 不可行输出-1,不要忘记换行
	}
	return 0;
}

B. Array Sharpening

题目大意是给一个长度不超过3e5的,元素都是自然数的数列。允许一种操作:使某个正数自减一。可以进行任意次这样的操作,最终要得到 先增后减/一直递减/一直递增 的数列。如果可行输出Yes,否则输出No。

因为只能通过减法将数列“削尖”,如果可行,则以某个位置为分隔点,它及它左边的数都要大于或等于它所处的位置下标,它及它右边的数都要大于等于倒着数的时候它所处的位置下标。这样才能保证最终能够得到“尖”的数列。

举个例子:0,1,2,3,4,5,4,3,2,1,0就是一个“尖”的数列。而0,1,1,3,4,5,4,3,2,1,0就无法通过减法得到“尖”的数列,因为只能对正数进行减法,即最小能得到0。

所以,可以用k1记录数列从左往右数第一个不满足数值大于下标的位置,用k2记录数列从右往左数第一个不满足数值大于倒数下标的位置(下标从零开始),如果 k 1 − 1 ≥ k 2 + 1 k_1-1 \ge k_2 + 1 k11k2+1,说明该数列可以被“削尖”。时间复杂度O(n)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cmath>
using namespace std;

#define maxn 300005

int f1[maxn], f2[maxn], l, r;
int n, t;

int main()
{
	cin >> t;	// 测试点数量
	while (t--)
	{
		int tmp;
		scanf("%d", &n); // 数列大小
		l = 0;	r = n - 1;// l记录数列从左往右数第一个不满足数值大于下标的位置,初始化为0;r记录数列从右往左数第一个不满足数值大于倒数下标的位置,初始化为n-1
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &tmp);
			f1[i] = (tmp >= i); // 满足置1,不满足置0
			f2[i] = (tmp >= n - 1 - i);// n-1-i为倒数下标
		}
        // 得到l和r
		while (f1[l] && l < n)	l++;
		while (f2[r] && r >= 0)	r--;
        // 输出
		if (l >= r + 2)	printf("Yes\n");// l - 1 >= r + 1
		else			printf("No\n");
	}
	return 0;
}

C. Mind Control

题目大意是有一个长度为n的数组,有n个人来取数,每个人可以从数组的头或者尾取一个数。已知自己是第m个取数,而且可以控制k个人一定从头取或者从尾端取,取数开始之后不得更改决定。求最大的x,满足无论没有被控制的人怎么取,我都可以取到不小于x的数。

前m-1个人有m种取数方法:从前面取0~m-1个数。轮到自己取数的时候,肯定会去取两头的最大的那个数,因为要使x尽可能大。因此自己取到的数有m种可能。如果没有“心灵控制”(没错我就是尤里),那么要考虑最坏的情况,则要从这m种可能中取最小值。

但因为有了“心灵控制”,所以问题就变复杂了。因为控制后面的人没有意义,为了方便,令k=min(m-1, k)。由于只有前面m-1-k个人无法控制,因此自己取到的数只有m-k种可能,x就是这m-k种可能中的最小值。而m-k种可能是原来那m种可能中连号的那几个。因此这就转化为求一个长度为m的数组,截取一段长度为m-k的片段,片段的最小值最大是多少的问题。

单个测试案例时间复杂度大概是O(nlogn)?

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cmath>
using namespace std;

#define maxn 3509

int a[maxn], d[maxn], res;
int t, n, m, k;

typedef struct
{
	int l, r;//左右端点下标
	long long w;//区间[l,r]的最小值
}Stree;

Stree stree[maxn << 2];

// 用了线段树来求最小值的最大值的问题
long long build(int k, int ll, int rr)//第k个区间建子树,返回区间的最小值
{
	stree[k].l = ll;
	stree[k].r = rr;
	if (ll == rr)
	{
		stree[k].w = d[ll];
		return stree[k].w;
	}
	int mid = (ll + rr) >> 1;
	stree[k].w = min(build((k << 1), ll, mid), build((k << 1) + 1, mid + 1, rr));
	return stree[k].w;
}

long long min_interval(int k, int ll, int rr)//对第k个区间与区间[ll,rr]交集的点的权重求和
{
	long long m = 0x3c3c3c3c;
	if (stree[k].l >= ll && stree[k].r <= rr)
	{
		return stree[k].w;
	}
	int mid = (stree[k].l + stree[k].r) >> 1;
	if (mid >= ll)
	{
		m = min(m, min_interval(k << 1, ll, rr));
	}
	if (mid < rr)
	{
		m = min(m, min_interval((k << 1) + 1, ll, rr));
	}
	return m;
}

int main()
{
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d%d", &n, &m, &k);
		k = min(k, m - 1);
		res = 0;// res为输出的结果,每轮初始化为0
		for (int i = 0; i < n; i++)
		{
			scanf("%d", a + i);//输入原始数组
		}
		for (int i = 0; i < m; i++)
		{
			d[i] = max(a[i], a[i + n - m]);//得到m种可能性
		}
		build(1, 0, m - 1);//建树
		for (int i = 0; i <= k; i++)
		{
			res = max((long long)res, min_interval(1, i, m - k - 1 + i));// 求最小值的最大值
		}
		printf("%d\n", res);
	}
	return 0;
}

小结

还是不够熟练,还是要多储备(找回以前的线段树模板都要找半天)。

太慢了,做这三道题都要花了两个小时。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值