Codeforces Round #616 (Div. 2)
Posted kisekipurin2019
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codeforces Round #616 (Div. 2)相关的知识,希望对你有一定的参考价值。
题目链接:https://codeforces.com/contest/1291
好演哦,可能我累了吧,只会AB,C吃完KFC之后就懂了233。可能是这次的2019-nCoV让我也很不正常吧。
A - Even But Not Even
题意:给一个十进制数字字符串,从中取一个非空子序列,使得这个子序列代表的数字不是偶数,但各个数字位加起来的和是偶数。
题解:不是偶数要求结尾一定是奇数,再要求各个数字位的和是偶数所以至少需要两个奇数,而两个奇数确实也满足题意。
char s[200005];
char t[200005];
void test_case() {
int n;
scanf("%d%s", &n, s + 1);
int top = 0;
for(int i = 1; i <= n; ++i) {
if((s[i] - '0') % 2 == 1) {
t[++top] = s[i];
if(top >= 2)
break;
}
}
if(top <= 1)
puts("-1");
else
printf("%c%c
", t[1], t[2]);
}
B - Array Sharpening
想了很多种奇奇怪怪的贪心,都会被
2
7
1 2 4 3 2 1 0
8
0 1 2 4 3 2 1 0
这组数据hack。
事实上有个很显然的 (O(n^2)) 的解法,枚举每个peek的位置,那么构造的方案一定是两边是 (0) ,然后向中间走直到碰到peek位置为止逐个递增1。那么可以维护一个布尔值的前缀和和后缀和,分别表示它前面的数能不能表示 (0 1 2 ... i) 和它后面的数字能不能表示 (i i-1 i-2 ... 0) 。中间这个peek必须大于其左边和其右边的最大值。
int n;
int x[300005];
bool a[300005];
bool pa[300005];
bool d[300005];
bool pd[300005];
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i){
scanf("%d", &x[i]);
a[i]=0;
pa[i]=0;
d[i]=0;
pd[i]=0;
}
if(n == 1) {
puts("Yes");
return;
}
if(n == 2) {
if(x[1] == 0 && x[2] == 0) {
puts("No");
return;
}
puts("Yes");
return;
}
for(int i = 1; i <= n; ++i) {
if(x[i] >= i - 1)
a[i] = 1;
}
pa[1] = a[1];
for(int i = 2; i <= n; ++i)
pa[i] = pa[i - 1] & a[i];
for(int i = n; i >= 1; --i) {
if(x[i] >= n - i)
d[i] = 1;
}
pd[n] = d[n];
for(int i = n - 1; i >= 1; --i)
pd[i] = pd[i + 1] & d[i];
if(pa[n] || pd[1]) {
puts("Yes");
return;
}
/*for(int i=1;i<=n;++i)
printf("%d%c",(int)a[i],"
"[i==n]);
for(int i=1;i<=n;++i)
printf("%d%c",(int)pa[i],"
"[i==n]);
for(int i=1;i<=n;++i)
printf("%d%c",(int)d[i],"
"[i==n]);
for(int i=1;i<=n;++i)
printf("%d%c",(int)pd[i],"
"[i==n]);*/
for(int i = 2; i <= n - 1; ++i) {
if(x[i] >= max(i - 1, n - i) && pa[i - 1] && pd[i + 1]) {
puts("Yes");
return;
}
}
puts("No");
return;
}
C - Mind Control
题意:有 (n(1 leq n leq 3500)) 个人排成一个队列A,恰好有 (n) 个数字排成一个双端队列B,每次A队列队首的人就会选择双端队列B的队首队尾两端其中之一进行Pop。你在队列A的第 (m(1leq m leq n)) 个位置,你可以说服至多 (k(1leq k leq n)) 个人,让他固定拿双端队列B的队首或者队尾,求你能够拿到的数字的最大值。
题解:一个 (O(n)) 的做法。首先可以取 (k:=min(k,m-1)) ,因为排在你后面的人是不会影响的。然后考虑一个规律:
先看说服 (0) 个人:
基本情况1:假如前 (m-1) 个都拿双端队列B的队尾,自己可以选择的就是 (1) 和 (n-m+1) 其中之一,那么肯定选 (max(a[1],a[n-m+1])) 。
基本情况2:假如前 (m-2) 个都拿双端队列B的队尾, (1) 个人拿双端队列B的队首,自己可以选择的就是 (2) 和 (n-m+2) 其中之一,那么肯定选 (max(a[2],a[n-m+2])) 。
……
基本情况m-1:假如前 (m-1) 个人都拿双端队列B的队首,自己可以选择的就是 (m) 和 (n) 其中之一,那么肯定选 (max(a[m],a[n])) 。
上面这些情况都有可能发生,所以取这堆数的最小值。
再看说服 (1) 个人:
情况1‘:说服他必须拿队首,那么去除掉上面的基本情况1。
情况2‘:说服他必须拿队尾,则去除掉上面的基本情况m-1。
再看说服 (2) 个人:
情况1‘:说服两人都必须拿队首,那么去除掉上面的基本情况1和基本情况2。
情况2‘:说服两人分别拿队首队尾,那么去除掉上面的基本情况1和基本情况m-1。
情况3‘:说服两人都必须拿队尾,那么去除掉上面的基本情况m-2和基本情况m-1。
枚举每种说服 (k_i) 个人的情形可以用尺取的方法维护,比如说服 (2) 个人,就可以先求出情况1‘的最小值;然后加入基本情况2,去掉基本情况m-1,就求出情况2‘的最小值;然后加入基本情况1,去掉基本情况m-2,就求出情况3‘的最小值。那么枚举 (k_i) 就是一个 (O(n^2)) 的做法。
不过在这里就可以观察出,说服更多的人,只是让单个情况包含的基本情况变少,同时让尺取的步骤变多,说服更多的人可以让单次取最小值的区间最短,那么取最小值的区间越短肯定得到答案越大的机会越大。也就是说,通过最小值的贡献规律,就可以知道直接求出说服 (k) 个人的情况就可以了。
统计尺取的队列中的最小值,使用双栈队列可以做到均摊 (O(n)) 。
struct Queue {
static const int MAXN = 3500;
static const int INF = 1061109567;
int s1[MAXN + 5], s2[MAXN + 5];
int s1top, s2top, s1min;
void Clear() {
s1top = 0;
s2top = 0;
s2[0] = INF;
s1min = INF;
}
void Push(int x) {
s1[++s1top] = x;
s1min = min(s1min, x);
}
void Pop() {
if(s2top)
--s2top;
else {
while(s1top)
s2[++s2top] = min(s2[s2top - 1], s1[s1top--]);
--s2top;
s1min = INF;
}
}
int Size() {
return s1top + s2top;
}
int Min() {
return min(s2[s2top], s1min);
}
} q;
int n, m, k;
int a[3505];
void test_case() {
scanf("%d%d%d", &n, &m, &k);
k = min(k, m - 1);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
q.Clear();
for(int i = 1; i <= m - k; ++i)
q.Push(max(a[i], a[n - m + i]));
int ans = q.Min();
for(int i = m - k + 1; n - m + i <= n; ++i) {
q.Pop();
q.Push(max(a[i], a[n - m + i]));
ans = max(ans, q.Min());
}
printf("%d
", ans);
return;
}
以上是关于Codeforces Round #616 (Div. 2)的主要内容,如果未能解决你的问题,请参考以下文章
Codeforces Round #616 (Div. 2)
Codeforces Round #616 (Div. 2)