zoukankan      html  css  js  c++  java
  • Codeforces Round #715 (Div. 1)

    Codeforces Round #715 (Div. 1)

    Codeforces Round #715 (Div. 1)

    A

    三个串中必定存在两个串 (A,B),满足(max(min(number of 0 in A,number of 0 in B),min(number of 1 in A,number of 1 in B))ge n)

    可以反证如果不存在就很离谱

    也就是说我们构造一个 (S),使得 (A,B)(S) 的子序列,可以共用的部分 (ge n),那么最终的长度 (leq 3 imes n)

    B

    其实每个符合条件的序列可以被划分为若干段,以 (i) 为横坐标,(a_i) 为纵坐标,就是这样:

    每一段都是斜率为 (-1) 的线段且相邻段首尾相接。那么如果长度和切割点一定,这个序列是唯一的。这就是一个简单的组合数。然后可以像进制转换那样去确定每一个切割点...

    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    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, lim = 1e5, mx = 1e18;
    int n, k, p[N], in[N], num[N];
    signed main() {
        int T; read (T);
        int now = 1;
        for (int i = 1; i <= lim; ++i) {
            num[i] = now; now *= 2;
            if (now > mx) {
                for (int j = i + 1; j <= lim; ++j) num[j] = mx * 5;
                break;
            }
        } num[0] = 1;
        while (T--) {
            read (n), read (k);
            if (k > num[n]) { puts ("-1"); continue; }
            int p = 1, sum = 0, la = 0;
            for (int i = 1; i <= n; ++i) {
                sum += num[n - i];
                if (sum == k) {
                    for (int j = i; j > la; --j) printf ("%lld ", j);
                    for (int j = n; j > i; --j) printf ("%lld ", j); break;
                } else if (sum > k) {
                    sum -= num[n - i];
                    for (int j = i; j > la; --j) printf ("%lld ", j); la = i;
                }
            } puts ("");
        }
    }
    

    C

    补图中只要一条边有权值 (X) 就可以满足异或为 (0) 的要求。先处理补图的连通性。如果有某条边不影响连通性,就把权值赋到这条边上,不用算入答案(不会出现在生成树中),然后把确定的边加入执行 (Kruscal) 的过程。否则(一棵树)要把 (X) 算入答案,然后先把确定边加入,再类似次小生成树处理一个替换调整的过程,可以跳 (LCA) 或再弄一个并查集来维护

    如何处理补图的连通性?肯定存在一个点 (p),度数 (leq frac{m}{n}),那么补图中不与 (p) 直接相连的点 (leq frac{m}{n}),对于这些点暴力判断,复杂度即为线性。也可以用一种边删边处理的方法解决。

    D

    ((i,a_i)) 当成边,如果整个排列构成一个环,那么只需要随便选一个点,然后不断把当前点上的值归位即可,就像这样:

    我们将其称之为“太阳线”,一个端点引出的线段显然没有交。

    现在考虑多个环的情况,可以交换两个不同环上的两个点来合并成一个环。但交换会连出一些新的线,有可能会和“太阳线”相交。为了避免这种情况,我们先选定“中心点”,将其它所有点进行极角排序,对两个相邻点执行并查集的过程:如果在一个环中跳过,否则这两个环以这两点为媒介相连。容易发现这样构造不会和“太阳线”相交。

    为了使极角排序简单顺利进行,不妨选最左点。

    #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 = 2020;
    int n, p, fa[N], a[N], m, id[N], x[N], y[N]; double k[N];
    int get (int x) {
        return x == fa[x] ? x : fa[x] = get (fa[x]);
    }
    void merge (int x, int y) { fa[get (x)] = get (y); }
    bool cmp (int a, int b) { return k[a] < k[b]; }
    struct ans { int x, y; } o[N * 10]; int cnt;
    void work (int x, int y) {
        o[++cnt] = {x, y}, swap (a[x], a[y]);
    }
    signed main() {
        read (n); x[0] = 1e9;
        for (int i = 1; i <= n; ++i) fa[i] = i;
        for (int i = 1; i <= n; ++i) {
            read (x[i]), read (y[i]);
            read (a[i]), merge (i, a[i]);
            if (i != a[i] && x[i] < x[p]) p = i;
        }
        if (!p) return puts ("0"), 0;
        for (int i = 1; i <= n; ++i)
            if (i != a[i] && i != p) id[++m] = i;
        for (int i = 1; i <= m; ++i) {
            k[id[i]] = 1.0 * (y[id[i]] - y[p]) / (x[id[i]] - x[p]);
        }
        sort (id + 1, id + m + 1, cmp);
        for (int i = 1; i < m; ++i) {
            int fx = get (id[i]), fy = get (id[i + 1]);
            if (fx == fy) continue;
            work (id[i], id[i + 1]), fa[fx] = fy;
        }
        while (a[p] != p) work (p, a[p]);
        printf ("%d
    ", cnt);
        for (int i = 1; i <= cnt; ++i)
            printf ("%d %d
    ", o[i].x, o[i].y);
        return 0;
    }
    

    E

    每次操作会选一个 (x)(son(x)) 中权值最小的点 (y) 交换且 (a_x<a_y),那么交换后 (a_y) 依旧是最小的,那么操作不改变兄弟节点间的相对大小。借此可以确定树的 (dfs)

    可以把操作分为若干轮,第 (i) 轮把数字 (i) 从根节点一路往最小的儿子向下走,直到走不了。由此可以得出当前进行的操作次数。当前正在进行的是 (a_1-1) 轮,由此可以得出当前进行的操作次数为 (1)(a_1-1) 数字所在节点的深度之和。剩下的问题是如何判断无解。

    对于数字 ([1,a_1-2]),操作已经结束,每个点所在的位置一定(可自行yy其规律,即代码中的 (st) 数组)

    对于 (a_1-1) ,它的目标节点应当在当前所在节点的子树里,且它能够一直向上到根(即路径上权值都比它大)

    对于 ([a_1,n]) 所在的位置,它们的爸爸都得比自己小

    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    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, M = N << 1;
    int n, s, a[N], w[N], tot, dfn[N], t[N], d[N], fa[N], o;
    vector<pair<int, int> > g[N];
    int cnt, h[N], nxt[M], to[M]; int tp, st[N];
    void add (int u, int v) {
        to[++cnt] = v, nxt[cnt] = h[u], h[u] = cnt;
    }
    #define pb push_back
    void dfs (int u, int la) {
        dfn[u] = ++tot, t[tot] = u, d[u] = d[la] + 1, fa[u] = la;
        for (int i = h[u], v; i; i = nxt[i])
            if ((v = to[i]) != la) g[u].push_back ({a[v], v});
        sort (g[u].begin(), g[u].end());
        for (auto i : g[u]) dfs (i.second, u); st[++tp] = u;
    }
    void YES () {
        printf ("YES
    %lld
    ", s);
        for (int i = 1; i <= n; ++i) printf ("%lld ", dfn[i]);
    }
    void NO () { puts ("NO"), exit (0); }
    void goup (int p) {
        int x = w[p];
        while (fa[x]) {
            if (a[x] > a[fa[x]]) NO ();
            swap (a[x], a[fa[x]]), x = fa[x];
        }
        x = st[p];
        while (x) { if (x == w[p]) return; x = fa[x]; }
        NO ();
    }
    signed main() {
        read (n);
        for (int i = 1; i <= n; ++i) read (a[i]), w[a[i]] = i;
        for (int i = 1, u, v; i < n; ++i)
            read (u), read (v), add (u, v), add (v, u);
        dfs (1, 0); o = a[1];
        for (int i = 1; i < o; ++i) s += d[w[i]] - 1;
        for (int i = 1; i < o - 1; ++i) if (w[i] != st[i]) NO ();
        if (o > 1) goup (o - 1);
        for (int i = 1; i <= n; ++i)
            if (a[fa[i]] > a[i] && a[i] >= o - 1) NO ();
        return YES (), 0;
    }
    

    F

    考虑对于一个固定的 (k) 求出答案。先按照题意暴力的连边 ((x,y)) 表示限制 (a_x<a_y)。有这些个推导:

    1、如果 (u,v) 之间存在长度 (ge 2) 的路径,边 ((u,v)) 无用

    2、对于一个 (x),必须的边 ((x,r),r>x) 最多只有一条,左边亦然。因为如果有两条 ((x,r1),(x,r2),x<r1<r2)(x)(r2) 同在的区间也包含 (r1),那么 (r1,r2) 间也有一条边或路径,根据结论 (1)((x,r1)(x,r2)) 中有一条边无用

    3、如何找到这个唯一的 (l,r) 呢?在所有包含 (x) 的区间中找到右端点 (R) 最大的,(r) 即为区间 ((x,R])(a_x) 的后继。(l) 亦然。

    4、但结论 (2) 中唯一的 ((x,r)) 也不一定必要,可能存在路径 ((x,dots,t,r)) 并根据结论 (1) 去掉这条边。如何判断?容易发现这个 (t) 必定 (<x),这次我们找到包含 (r) 的左端点 (L) 最小的区间,如果存在 (p),满足 (Lleq p<x,a_x<a_p<a_r),那么这个 (p) 可以作为 (t) 而使 ((x,r)) 失效

    以上四点可以让我们解决一个固定的 (k) 了:先用 (3) 求出所有 (l,r),再用 (4) 判断哪些 (l,r) 无用,剩下的即为答案。但通过这种方法求出所有 (k) 对应的答案似乎太慢了。

    In fact,稍稍改进一下结论和做法就可以较快速地求出所有答案。我们维护的 (l,r) 就是向左向右的后继,不妨顺便把前驱也计算出来,把这四条边称为 “判定边”。结论 (4) 引理:一条边 ((x,y)) 不会被删除当且仅当它同时是 (x,y) 的 “判定边”。可以自行证明其充要性。每个点的 “判定边” 的另一端单调,最多改变 (O(n)) 次,可以在 (O(n^2)/O(nq)) 时间内维护(直接遍历 每个区间为 (nq),先把不用更改的区间扔掉再遍历为 (n^2),差别不大),可以通过本题。

    而官方题解的优秀做法就是处理一下每条边作为 “判定边” 的时间段然后 (set)+莫队 一顿操作弄到 (O(nsqrt{q} logn))。反正特别麻烦,所以只写了非官方做法(下面的代码常数较大)

    #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 = 25005;
    int n, m, res, a[N], L[N], R[N], la[N][2], ra[N][2];
    void work (int l, int r) {
        for (int i = l; i <= r; ++i) {
            for (int j = R[i] + 1; j <= r; ++j) {
                int t = ra[i][0];
                if (a[j] < a[i] && a[j] > a[t])
                    res += (la[j][1] == i) - (la[t][1] == i), ra[i][0] = j;
                t = ra[i][1];
                if (a[j] > a[i] && a[j] < a[t])
                    res += (la[j][0] == i) - (la[t][0] == i), ra[i][1] = j;
                R[i] = j;
            }
            for (int j = L[i] - 1; j >= l; --j) {
                int t = la[i][0];
                if (a[j] < a[i] && a[j] > a[t])
                    res += (ra[j][1] == i) - (ra[t][1] == i), la[i][0] = j;
                t = la[i][1];
                if (a[j] > a[i] && a[j] < a[t])
                    res += (ra[j][0] == i) - (ra[t][0] == i), la[i][1] = j;
                L[i] = j;
            }
        }
    }
    signed main() {
        read (n), read (m); a[n + 1] = n + 1;
        for (int i = 1; i <= n; ++i) read (a[i]);
        for (int i = 1; i <= n; ++i)
            R[i] = L[i] = i, la[i][1] = ra[i][1] = n + 1;
        for (int i = 1, l, r; i <= m; ++i) {
            read (l), read (r);
            work (l, r); printf ("%d
    ", res);
        }
        return 0;
    }
    
  • 相关阅读:
    剖析VC++函数调用约定转
    C++的坑真的多吗?转
    An Introduction to LockFree Programming转
    __cdecl __stdcall区别转
    学习PHP感谢帅哥分享O(∩_∩)O~
    28个Unix/Linux的命令行神器转
    C++ 对象的内存布局(上)转
    一个fork的面试题转
    20本最好的Linux免费书籍转
    谁说外国人都很文明
  • 原文地址:https://www.cnblogs.com/whx666/p/715-div1.html
Copyright © 2011-2022 走看看