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\n", 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]," \n"[i==n]);
for(int i=1;i<=n;++i)
printf("%d%c",(int)pa[i]," \n"[i==n]);
for(int i=1;i<=n;++i)
printf("%d%c",(int)d[i]," \n"[i==n]);
for(int i=1;i<=n;++i)
printf("%d%c",(int)pd[i]," \n"[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(1\leq m \leq n)\) 个位置,你可以说服至多 \(k(1\leq 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\n", ans);
return;
}
收获极大,题解说可以用单调队列,给了一点启示!原来维护队列中的最值直接用单调队列就ok了,可能在维护gcd或者矩阵乘法才需要双栈队列吧。
D - Irreducible Anagrams
题意:称两个字符串是“重排等价的”,当且仅当它们sort之后全等。称两个“重排等价”的字符串 \(s,t\) 是“可再分”的,当且仅当不存在一种分割 \(s=s_1+s_2+...+s_k\) 和 \(t=t_1+t_2+...+t_k\) 其中每个对应部分都“重排等价”。给一个字符串 \(s\) ,每次询问一个区间,问这个区间对应的子串是否是拥有至少一个“不可再分”的“重排等价串”。
题解:题目的意思,好像是和这个子串 \(t\) 本身的顺序无关,题目只是问这个串是否有一个“不可再分”的“重排等价串”,并不是问这个子串是不是“重排等价串”。(X,“重排等价”关系是和顺序无关,但是“不可再分”关系是和 \(t\) 的顺序有关的)
可以看出来只需要分成两个部分都不“重排等价”就是不可再分的,因为分成多个部分,可以把若干个合在一起归纳成两个部分。
显然,假如某个字符串只有1个字符,那么肯定拥有一个“不可再分”的“重排等价”,就是它本身,输出YES。
否则,若是有两个字符。假如只有一种字符,那么总是可以在中间切成两半,使得前后两半各自“重排等价”,这个可以推广到更多字符的情形,所以有两个或更多字符时,只有一种字符,输出NO。否则,恰好有两种字符,若是 "ab" ,则 "ba" 就是一个“不可再分”的“重排等价”,输出YES。
否则,若是有三个字符。假如只有两种字符,若是 "abb" ,则 "bba" 就是一个“不可再分”的“重排等价”,若是 "aba" ,则无解,因为新的串头和尾至少有一个是 "a" ,假如是三种字符,那么 "abc" 和 "cba" 符合题意。
若是有四个字符,大概从这里可以开始归纳了,若是首尾不同的字符串,那么只交换首和尾,中间保持不动,就是一种构造(在中间任何位置,前后都恰好差一个首部和尾部),或者把首或尾的同种字符全部移动到前部,这样直到 \(s\) 的尾部,都不会达到足够多的这种字符,所以首尾不同必定有解。其他情况一定是首尾相同的比如 "abca" 和 "abba" ,对于前一种情况 "caab" 确实是一种构造,对于后一种情况是无解的:首先首和尾不能放 'a' ,所以一定是放成 "baab" 而 "baab"="ba|ab" ,它和 "ab|ba" 是前后对应等价的。这里可以看出来,假如只有两种字符,那么在某个位置一定会对应等价(离散介值定理)。 对于有三种字符的情况,找到第一次出现的第三种字符,把全部同种字符拉到最前面,然后把原本的首尾同种字符拉到中间,比如 "abccaa" ,就是 "ccaaab" ,比如 "abccbaba" 就是 "ccaaabbb" 这种一定是正确的,因为要使得前缀匹配需要找到一个 'a' ,而向中间走了之后又多了一个要找 'b' ,在找完 'a'之前都不会有 'b' ,在找完 'a' 之后,直到匹配完整个串,都不会有相同数量的 'a' 匹配。
char s[200005];
int cnt[26][200005];
void test_case() {
scanf("%s", s + 1);
int n = strlen(s + 1);
for(int i = 1; i <= n; ++i) {
for(int j = 0; j < 26; ++j)
cnt[j][i] = cnt[j][i - 1];
cnt[s[i] - 'a'][i] = cnt[s[i] - 'a'][i - 1] + 1;
}
int q;
scanf("%d", &q);
while(q--) {
int l, r;
scanf("%d%d", &l, &r);
if(l == r || s[l] != s[r]) {
puts("Yes");
continue;
} else {
int cnttype = 0;
for(int j = 0; j < 26; ++j) {
if(cnt[j][r] - cnt[j][l - 1])
++cnttype;
}
if(cnttype >= 3)
puts("Yes");
else
puts("No");
continue;
}
}
}