zoukankan      html  css  js  c++  java
  • 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;
    }
    

    收获极大,题解说可以用单调队列,给了一点启示!原来维护队列中的最值直接用单调队列就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;
            }
        }
    }
    
  • 相关阅读:
    Linux下编辑、编译、调试命令总结——gcc和gdb描述
    scanf函数读取缓冲区数据的问题
    Windows下设置Ubuntu引导项
    前端术语汇总笔记(会保持更新)
    实现动态加载一个 JavaScript 资源
    提取一个字符串中的数字,并将其转为数组
    CSS3图片倒影技术
    js函数聚合
    js继承函数封装
    联动菜单实现思路
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12254480.html
Copyright © 2011-2022 走看看