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 k1−1≥k2+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;
}
小结
还是不够熟练,还是要多储备(找回以前的线段树模板都要找半天)。
太慢了,做这三道题都要花了两个小时。