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;
        }
    }
}
posted @ 2020-02-03 01:27  KisekiPurin2019  阅读(196)  评论(0编辑  收藏  举报