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\n", (k - 1) / 2);
        return;
    }
    if(n <= k / 2) {
        printf("0\n");
        return;
    }
    ll q = k - (n + 1);
    printf("%lld\n", (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]='\0';
                --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], " \n"[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], " \n"[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\n", 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('\n');
    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.怎么区分自己的边和对手的边?直接一视同仁建在树里面更新最大值,不过最后统计的时候把这些边去除。

posted @ 2019-12-03 11:54  KisekiPurin2019  阅读(147)  评论(0编辑  收藏  举报