W
H
X

Educational Codeforces Round 103 EFG 题解

Educational Codeforces Round 103

E - Pattern Matching

在每一个限制条件中,\(s_j\) 可以和 \(16\) 个串匹配(每个位置是不是 _ ,\(2^4=16\))。把这些串都找出来,要求 \(mt_j\) 排在最前面,从其它串向 \(mt_j\) 连一条边,表示先后先后关系,然后一遍拓扑

#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
    char ch = getchar(); int f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar(); }
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
    if (f) x = -x;
} const int N = 1e5 + 5;
int n, m, k, p[N]; char a[N][5], b[5], c[5];
int f (char *p) {
    return p[0] * 1000000 + p[1] * 10000 + p[2] * 100 + p[3];
}
struct qwq { int id, num; } u[N]; int uu[N];
bool cmp (qwq a, qwq b) { return a.num < b.num; }
vector<int> g[N]; int o[N];
void add (int u, int v) { g[u].push_back (v), ++o[v]; }
queue<int> q;
void work (int w) {
    for (int i = 0; i < (1 << k); ++i) {
        int tag = 0;
        for (int j = 0; j < k; ++j)
            if ((i >> j) & 1) c[j] = b[j]; else c[j] = '_';
        int cc = f (c);
        int t = lower_bound (uu + 1, uu + n + 1, cc) - uu;
        if (uu[t] == cc && u[t].id != w) add (u[t].id, w);
    }
}
signed main() {
    read (n), read (m), read (k);
    for (int i = 1; i <= n; ++i) scanf ("%s", a[i]);
    for (int i = 1; i <= n; ++i) u[i].id = i, u[i].num = f (a[i]);
    sort (u + 1, u + n + 1, cmp);
    for (int i = 1; i <= n; ++i) uu[i] = u[i].num;
    for (int i = 1, w; i <= m; ++i) {
        scanf ("%s %d", b, &w), work (w);
        for (int j = 0; j < k; ++j)
            if (a[w][j] != '_' && a[w][j] != b[j]) { return puts ("NO"), 0; }
    }
    for (int i = 1; i <= n; ++i) if (!o[i]) q.push (i);
    int now = n;
    while (!q.empty()) {
        int u = q.front(); q.pop(); p[now--] = u;
        for (int v : g[u]) if (!(--o[v])) q.push (v);
    }
    if (now) { return puts ("NO"), 0; } puts ("YES");
    for (int i = 1; i <= n; ++i) printf ("%d ", p[i]); puts ("");
}

F - Lanterns

\(f_i\) 表示前 \(i\) 盏灯笼能覆盖的最长前缀。如何转移?

枚举 \(j\) ,如果 \(f_j \ge i\),则 \((j,i]\) 区间的全都可以朝右,答案为 \(max_{i=j+1}^{i}{i+a_i}\)

如果 \(f_j\ge i-a_i\),则 \(i\) 朝左,\((j,i)\) 内的全部超右,答案类似

由于 \(f_j\) 是单调的,所以符合条件的 \(j\) 的边界可以二分出来,然后记录一下是从哪里转移的就可以输出方案了

#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
    char ch = getchar(); int f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar(); }
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
    if (f) x = -x;
} const int N = 3e5 + 5;
int n, a[N], f[N], fr[N], d[N], k[N], lg[N];
#define cmax(x, y) (x = x > y ? x : y)
struct ans { int v, w; } ;
struct st {
    int c[N][19];
    void pre () {
        for (int i = 0; i <= n; ++i) c[i][0] = a[i] + i;
        for (int i = 1; (1 << i) <= n; ++i)
            for (int j = 1; j + (1 << i) - 1 <= n; ++j)
                c[j][i] = max (c[j][i - 1], c[j + (1 << i - 1)][i - 1]);
    }
    int query (int l, int r) {
        if (l > r) return 0; int t = lg[r - l + 1];
        // printf ("%d %d %d\n", l, r, max (c[l][t], c[r - (1 << t) + 1][t]));
        return max (c[l][t], c[r - (1 << t) + 1][t]);
    }
} A;
signed main() {
    int T; read (T); lg[0] = -1;
    for (int i = 1; i <= 300002; ++i) lg[i] = lg[i >> 1] + 1;
    while (T--) {
        read (n); a[++n] = 0;
        for (int i = 1; i < n; ++i) read (a[i]); A.pre ();
        for (int i = 0; i <= n; ++i) f[i] = k[i] = fr[i] = 0;
        for (int i = 1, l, r, m, j, mx = -1, w = 0; i <= n; ++i) {
            l = 0, r = i - 1, j = -1;
            while (l <= r) {
                m = l + r >> 1;
                if (f[m] + 1 >= i - a[i]) j = m, r = m - 1;
                else l = m + 1;
            }
            if (j >= 0) f[i] = max (A.query (j + 1, i - 1), i - 1), fr[i] = j;
            if (f[i - 1] >= i && f[i] < i + a[i]) f[i] = i + a[i], fr[i] = i - 1, k[i] = 1;
            if (mx > f[i]) f[i] = mx, fr[i] = w; else mx = f[i], w = i;
            // printf ("666 %d %d %d %d\n", j, i, f[i], fr[i]);
        }
        if (f[n] >= n - 1) {
            puts ("YES");
            for (int i = n; i >= 1; i = fr[i]) {
                for (int j = fr[i] + 1; j < i; ++j) d[j] = 1; d[i] = k[i];
            }
            for (int i = 1; i < n; ++i) putchar (d[i] ? 'R' : 'L'); puts ("");
        } else puts ("NO");
    }
}

G - Minimum Difference

使用莫队解决,那么操作 \(2\) 就是把普通莫队换成回滚莫队,可以不用管

根据题面肯定得维护 \(cnt\) 数组记录每个数出现的次数

然后还得维护 \(num\) 维护每种 \(cnt\) 出现的次数

例如,当前的数列为 \(1,1,1,2,2,2,3\),则 \(cnt={3,3,1}\)\(num={1,0,2}\) (从 \(1\) 开始)

如果单单维护 \(num,cnt\) 很简单,但仅仅维护两个数组并不能快速回答询问

先讲讲询问的回答方法:\(cnt\) 的种类的数量是 \(O(\sqrt{n})\) 的(\(1+2+3+...+2\sqrt{n}\ge n\)),所以需要把这些有用的状态提取出来,然后就可以通过指针扫描 \(O(\sqrt{n})\) 回答单词询问

维护有用的 \(num\) 可以转换成维护一些区间, \(num\) 中的值就是这些区间的长度,当 \(cnt\) 改变的时候只需要 \(O(1)\) 维护区间边界。对于每次询问“跳”区间直到超出边界,所以只有非空的区间才会遍历到

总复杂度 \(O(n^{\frac{5}{3}}+m\sqrt{n})\)

#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 3e5 + 5;
int n, m, sz, nn, a[N], bl[N], res[N];
struct qwq {
    int l, r, k, c, id;
    bool operator < (const qwq &p) const {
        return bl[l] ^ bl[p.l] ? bl[l] < bl[p.l] : bl[r] ^ bl[p.r] ? bl[r] < bl[p.r] : id < p.id;
    }
} b[N];
struct qaq { int w, x, y; } c[N]; int nb, nc;
int l = 1, r = 0, cnt[N], ll[N], rr[N], co[N], s[N], cc[N];
void change (int x) {
    co[rr[cnt[x]]] = cnt[x] + 1, --rr[cnt[x]], --ll[cnt[x] + 1], ++cnt[x];
}
void _change (int x) {
    co[ll[cnt[x]]] = cnt[x] - 1, ++ll[cnt[x]], ++rr[cnt[x] - 1], --cnt[x];
}
void modify (int p) {
    if (l <= c[p].w && c[p].w <= r) change (c[p].y), _change (c[p].x); a[c[p].w] = c[p].y;
}
void _modify (int p) {
    if (l <= c[p].w && c[p].w <= r) change (c[p].x), _change (c[p].y); a[c[p].w] = c[p].x;
}
int query (int k) {
    int u = 0, res = n + 1;
    for (int i = 1; i <= n; i = rr[co[i]] + 1)
        if (co[i]) s[++u] = rr[co[i]] - ll[co[i]] + 1, cc[u] = co[i];
    for (int i = 1, j = 0, ss = 0; i <= u; ++i) {
        while (ss < k && j < u) ++j, ss += s[j];
        if (ss >= k) res = min (res, cc[j] - cc[i]); ss -= s[i];
    }
    return res > n ? -1 : res;
}
signed main() {
    read (n), read (m);

    sz = pow (n, 0.666); nn = n / sz; int L = 0, R = 0;
    for (int i = 1; i <= nn; ++i) {
        L = R + 1, R += sz;
        for (int j = L; j <= R; ++j) bl[j] = i;
    }
    for (int i = R + 1; i <= n; ++i) bl[i] = nn + 1;

    for (int i = 1; i <= n; ++i) read (a[i]);
    for (int i = 1, o, l, r, k; i <= m; ++i) {
        read (o);
        if (o == 1) read (l), read (r), read (k), b[++nb] = { l, r, k, nc, nb };
        else read (r), read (k), c[++nc] = { r, a[r], k }, a[r] = k;
    }
    sort (b + 1, b + nb + 1);

    int now = nc; ll[0] = 1, rr[0] = n;
    for (int i = 1; i <= n; ++i) ll[i] = n + 1, rr[i] = n;
    for (int i = 1; i <= nb; ++i) {
        // printf ("2333 %d %d %d %d\n", b[i].id, b[i].l, b[i].r, b[i].k);
        while (now < b[i].c) ++now, modify (now);
        while (now > b[i].c) _modify (now), --now;
        while (l < b[i].l) _change (a[l]), ++l;
        while (l > b[i].l) --l, change (a[l]);
        while (r < b[i].r) ++r, change (a[r]);
        while (r > b[i].r) _change (a[r]), --r;
        res[b[i].id] = query (b[i].k);
    }
    for (int i = 1; i <= nb; ++i) printf ("%d\n", res[i]);
    return 0;
}

posted @ 2021-02-01 19:54  -敲键盘的猫-  阅读(150)  评论(0编辑  收藏  举报