• Codeforces Round #504 (rated, Div. 1 + Div. 2, based on VK Cup 2018 Final)


    A - Single Wildcard Pattern Matching

    题意:*可以换成任意的一段字符串,s串只有一个*,t串没有*,问是否可以从s串变为t串。

    题解:意思就是*前面的要和t的前段完全匹配,*后面的也要和t的后段完全匹配,且两端不能有交叉。

    char s[200005], t[200005];
    
    void test_case() {
        int n, m;
        scanf("%d%d%s%s", &n, &m, s + 1, t + 1);
        if(m < n - 1) {
            puts("NO");
            return;
        }
        int x = -1;
        for(int i = 1; i <= n; ++i) {
            if(s[i] == '*') {
                x = i;
                break;
            }
        }
        if(x == -1) {
            if(strcmp(s + 1, t + 1) == 0)
                puts("YES");
            else
                puts("NO");
            return;
        }
        for(int i = 1; i <= x - 1; ++i) {
            if(s[i] != t[i]) {
                puts("NO");
                return;
            }
        }
        for(int i = n, j = m; i >= x + 1; --i, --j) {
            if(s[i] != t[j]) {
                puts("NO");
                return;
            }
        }
        puts("YES");
    }
    

    B - Pair of Toys

    题意:用两个不同的且不超过n的数a,b凑出a+b=k,求有多少种方法。

    题解:假如去掉所有限制,则就是k-1种方法。k是偶数会额外丢失一种相等的方法。由于n不够大会截断最开始的 1 k-1 这样悬殊的方法。

    void test_case() {
        ll n, k;
        scanf("%lld%lld", &n, &k);
        if(n >= k - 1) {
            printf("%lld
    ", (k - 1) / 2);
            return;
        }
        if(n <= k / 2) {
            printf("0
    ");
            return;
        }
        ll q = k - (n + 1);
        printf("%lld
    ", (k - 1) / 2 - q);
    }
    

    C - Bracket Subsequence

    题意:给一个n长度的合法括号串,选其中一个长度恰好为k(k<=n)的子序列,这个子序列也是合法括号串。

    题解:每次去掉一对括号不就减少了2长度了吗?可以每次检测到匹配的时候优先删除。

    char s[200005];
    char t[200005];
    
    void test_case() {
        int n, k;
        scanf("%d%d%s", &n, &k, s + 1);
        int top = 0;
        int rest = n - k;
        for(int i = 1; i <= n; ++i) {
            if(s[i] == '(')
                t[++top] = '(';
            else {
                if(rest) {
                    t[top]='';
                    --top;
                    rest -= 2;
                } else
                    t[++top] = ')';
            }
        }
        puts(t + 1);
    }
    

    注意打上结尾符。

    D - Array Restoration

    题意:一个n个元素的数组,恰好进行q次修改,第i次修改可以把连续的一段区间赋值为i。求是否可以构造出题目提供的数组,0表示可以填任意值。

    题解:
    先解决没有0的情况的问题。每次都是区间更新一次,那么查询到最左的i和最右的i(假如存在),那么中间部分的最小值必须>=i,这个看起来就像线段树查询,实际上用st表也可以。当所有的查询都满足并且最大值q存在,那么合法(中间缺的值全都由最大值覆盖掉)。

    有0的情况稍微复杂一些,首先假如最大值q不存在,那么随便赋值任意一个0变成最大值就可以了。如果中间的段有0的话,就把这些0强制赋值为i,注意到这样子不会使区间的最小值变大,也就是这样构造是最优的,既使得lr之间连续,也满足无后效性。假如还有0剩余就把0赋值为左右相同的值,但是假如左右也是0怎么办呢?不妨把a[0]设为1,然后每个0的值都从其左边继承。

    实现的时候就是一棵最小值线段树和一棵区间更新线段树。最后把标记全部下传给叶子。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int MAXN = 200000;
    const int INF = 0x3f3f3f3f;
    int a[MAXN + 5];
    
    struct SegmentTree1 {
    #define ls (o<<1)
    #define rs (o<<1|1)
        static const int MAXN = 200000;
        static const int INF = 0x3f3f3f3f;
        int mi[(MAXN << 2) + 5];
    
        void PushUp(int o) {
            mi[o] = min(mi[ls], mi[rs]);
        }
    
        void Build(int o, int l, int r) {
            if(l == r) {
                mi[o] = (a[l] == 0 ? INF : a[l]);
            } else {
                int m = l + r >> 1;
                Build(ls, l, m);
                Build(rs, m + 1, r);
                PushUp(o);
            }
        }
    
        int QueryMin(int o, int l, int r, int ql, int qr) {
            if(ql <= l && r <= qr) {
                return mi[o];
            } else {
                int m = l + r >> 1;
                int res = INF;
                if(ql <= m)
                    res = QueryMin(ls, l, m, ql, qr);
                if(qr >= m + 1)
                    res = min(res, QueryMin(rs, m + 1, r, ql, qr));
                return res;
            }
        }
    #undef ls
    #undef rs
    } st1;
    
    struct SegmentTree2 {
    #define ls (o<<1)
    #define rs (o<<1|1)
        static const int MAXN = 200000;
        static const int INF = 0x3f3f3f3f;
        int a[MAXN + 5], lz[(MAXN << 2) + 5];
    
        void PushDown(int o, int l, int r) {
            if(lz[o]) {
                lz[ls] = lz[o];
                lz[rs] = lz[o];
                lz[o] = 0;
            }
        }
    
        void Build(int o, int l, int r) {
            if(l == r) {
                a[l] = 0;
            } else {
                int m = l + r >> 1;
                Build(ls, l, m);
                Build(rs, m + 1, r);
            }
            lz[o] = 0;
        }
    
        void Update(int o, int l, int r, int ql, int qr, int v) {
            if(ql <= l && r <= qr) {
                lz[o] = v;
            } else {
                PushDown(o, l, r);
                int m = l + r >> 1;
                if(ql <= m)
                    Update(ls, l, m, ql, qr, v);
                if(qr >= m + 1)
                    Update(rs, m + 1, r, ql, qr, v);
            }
        }
    
        void PushDownAll(int o, int l, int r) {
            if(l == r) {
                a[l] = lz[o];
            } else {
                PushDown(o, l, r);
                int m = l + r >> 1;
                PushDownAll(ls, l, m);
                PushDownAll(rs, m + 1, r);
            }
        }
    #undef ls
    #undef rs
    } st2;
    
    int n, q, maxai, lm[MAXN + 5], rm[MAXN + 5];
    
    void solve0() {
        if(maxai != q) {
            puts("NO");
            return;
        }
        st1.Build(1, 1, n);
        for(int i = 1; i <= q; ++i) {
            if(lm[i] == 0)
                continue;
            if(st1.QueryMin(1, 1, n, lm[i], rm[i]) < i) {
                puts("NO");
                return;
            }
        }
        puts("YES");
        for(int i = 1; i <= n; ++i)
            printf("%d%c", a[i], " 
    "[i == n]);
    }
    
    void solve1() {
        st1.Build(1, 1, n);
        for(int i = 1; i <= q; ++i) {
            if(lm[i] == 0)
                continue;
            if(st1.QueryMin(1, 1, n, lm[i], rm[i]) < i) {
                puts("NO");
                return;
            }
        }
        if(lm[q] == 0)
            a[lm[0]] = q;
        st2.PushDownAll(1, 1, n);
        a[0] = 1;
        for(int i = 1; i <= n; ++i) {
            if(a[i] == 0)
                a[i] = st2.a[i];
            if(a[i] == 0)
                a[i] = a[i - 1];
        }
        puts("YES");
        for(int i = 1; i <= n; ++i)
            printf("%d%c", a[i], " 
    "[i == n]);
    }
    
    void test_case() {
        scanf("%d%d", &n, &q);
        maxai = 0;
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            if(a[i] > maxai)
                maxai = a[i];
            if(lm[a[i]] == 0)
                lm[a[i]] = i;
            rm[a[i]] = i;
        }
        if(lm[0] == 0)
            solve0();
        else
            solve1();
    }
    
    int main() {
    #ifdef KisekiPurin
        freopen("KisekiPurin.in", "r", stdin);
    #endif // KisekiPurin
        int t = 1;
        //scanf("%d", &t);
        for(int ti = 1; ti <= t; ++ti) {
            //printf("Case #%d: ", ti);
            test_case();
        }
    }
    
    /*
        1. 小数据问题退化:
            输入为0或1会不会有特殊情况?其他的比如最小环要有三个点,强连通分量缩到最后一个点等等。
        2. 内存:
            内存的空间有没有开够?有时有反向边,有时有额外新加的边。线段树开了4倍了吗?
            可持久化数据结构会不会内存溢出?多组数据时vector会不会翻车?字符大小写有考虑吗?
            多组数据有进行初始化吗?memset会不会翻车?
        3. 算术溢出:
            乘法上溢出了?忘记取模了?输入输出用了%d?让无符号数越到负数了?
        4. 习惯:
            函数都有指定返回值吗?返回值非void函数的每一个分支都有显式的返回值吗?确定不会进入的分支可以assert一把。
            Yes和No有没有反,大小写有没有错?有没有搞错n和m?离散化之后的cn有没有错?换行和空格要怎么整?priority要把符号反过来。
        5. 其他bug:
            基环树是树加环,不是链加环。
    */
    

    修改0的时候可以使用差分来做,但是没必要,合法的操作序列一定是像一个括号序列一样的,不会有4545这样的修改,所以可以用个栈来记录左右端点,把中间遇到的0全部设置为栈顶。但是第一步的查询最小值貌似是莫队可以搞,说起来还没学过莫队。

    总结:不带查询的区间修改永远不需要线段树,直接打差分就可以了,不过没必要。

    E - Down or Right

    交互题,真恶心。

    不会做这种恶心题,听敦爷的,直接看题解。

    题意:有一个n*n(n<=500)的矩阵。每次只能向下走或者向右走,每次询问起点和终点,评测机告诉能不能走。最多询问4*n次,每次询问的起点终点的曼哈顿距离必须超过n-1,最后给出从(1,1)到(n,n)的路线。保证必定存在至少一条路线。

    题解:若去除距离限制,那么每次询问当前点(r,c)的下边点(r+1,c)是否可达(n,n),可达则向下走,否则一定可以向右走。这样可以一直走到反对角线上最左侧的可达点。然后反过来每次询问(1,1)是否能够到达当前点(r,c)的左边点(r,c-1),可达则向左走,否则一定可以向上走。这样会在反对角线上最左侧的可达点汇合。花费2*(n-1)次询问。

    int n;
    char s[105];
    bool query(int a, int b, int c, int d) {
        printf("? %d %d %d %d
    ", a, b, c, d);
        fflush(stdout);
        scanf("%s", s);
        return s[0] == 'Y';
    }
    
    char ans1[505];
    char ans2[505];
    
    void test_case() {
        scanf("%d", &n);
        int r = 1, c = 1, t = 0;
        while(r + c <= n) {
            if(r + 1 <= n && query(r + 1, c, n, n)) {
                ++r;
                ans1[++t] = 'D';
            } else {
                ++c;
                ans1[++t] = 'R';
            }
        }
        r = n, c = n, t = 0;
        while(r + c - 1 > n) {
            if(c - 1 >= 1 && query(1, 1, r, c - 1)) {
                --c;
                ans2[++t] = 'R';
            } else {
                --r;
                ans2[++t] = 'D';
            }
        }
        reverse(ans2 + 1, ans2 + 1 + t);
        printf("! ");
        for(int i = 1; i <= t; ++i)
            putchar(ans1[i]);
        for(int i = 1; i <= t; ++i)
            putchar(ans2[i]);
        putchar('
    ');
        fflush(stdout);
    }
    

    F - Mobile Phone Network

    题意:有n个点,你的竞争对手有m条边(u,v,w),你有k条边(u,v)组成一个森林,给这些边定价,使得这个图的MST包含你所有的边,注意当你的边和竞争对手的边价格相同时,MST还是会自动选择你的边。

    题解:先把你的所有边的森林弄出来,加入对手的边连成一棵生成树。然后再加入对手的边e就一定会成环,那么在这个环上的你的边的价格就不能超过e的w,所以可以用一个巨复杂的边树剖来维护这个东西。注意到线段树可以尝试区间修改一段区间的最大值限制,一开始初始化最大值限制为无穷,然后每次当前的lazy比更新值v大的时候就把整个lazy换掉。最后把所有lazy下推统计。

    那么细节就有好几个点:

    1.在连成生成树之前有可能会成环,这个时候可以先跳过这个环,等生成树建出来后再更新最小值。

    2.怎么区分自己的边和对手的边?直接一视同仁建在树里面更新最大值,不过最后统计的时候把这些边去除。

  • 相关阅读:
    基本操作——word中怎样同一页中放入多张图片
    计算机概念入门(二)
    计算机概念入门(一)
    关于黑苹果系统的耳机声音模糊不清问题
    开源项目
    尚硅谷官网资料导航
    this
    最棒的 JavaScript 学习指南(2018版)(转)
    前端文摘:深入解析浏览器的幕后工作原理(转)
    闭包,模块化
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/11975970.html
Copyright © 2020-2023  润新知