• Solution Set 「NOIP Simu.」20221003


    \(\mathscr{A}\sim\) 二分图排列

      定义一个数列 \(\{a_n\}\) 合法, 当且仅当无向图 \(G=(\{1..n\},\{(i,j)\mid i<j\land a_i>a_j\})\) 是二分图. 现给定 \(1\sim n\) 的排列 \(\{a_n\}\), 你可以将其中任意多个数取相反数得到 \(\{a_n'\}\), 求所有合法的 \(\{a_n'\}\) 中字典序最小的一个.

      多测, \(\sum n\le10^6\).


      Tags:「A.DP-序列 DP」「C.性质/结论」

      显然, 若 \(G\) 中存在大小为 \(k\) 的环, 则一定存在大小为 \(k-1\) 的环. 因而合法条件等价于不存在三元环, 也即是不存在 \(i<j<k\) 使得 \(a_i'>a_j'>a_k'\).

      先来看看怎么判断合法. 我们从左到右扫描 \(\{a_n'\}\), 扫描到 \(i\) 时, 维护出一个数对 \((u,d)\), 其中 \(u=\max_{j<i}\{a_j'\}\), \(d=\max_{j<i}\{a_j'\mid \exists k<j,~a_k'>a_j'\}\). 可见, \(d>a_i\) 意味着非法三元组的出现; 若 \(a_i>u\), 则 \(u\gets a_i\), 否则 \(d\gets a_i\). 如果扫描顺利完成, 这个序列就是合法的.

      现在, 我们想要构造字典序最小的 \(\{a_n'\}\), 求解过程中, 我们会尝试确定一段前缀 \(a_{1..i-1}'\), 并决策当前的数取 \(+a_i\)\(-a_i\), 同时检查后缀能否在此基础上取出合法序列. 那么, 对应到合法判断方式, 我们会将扫描到 \(a_{1..i}'\) 时获得的 \((u,d)\) 作为后缀的输入数对, 判断这个输入数对能否在后缀中通过判断.

      接下来就是状态设计. 最原始的自然是 \(f(i,u,d)\) 表示从 \(i\) 开始, 输入数对为 \((u,d)\) 时, 后缀是否有合法解; 注意到 \(u,d\) 在对方固定时, 自己越大越劣, 所以可以优化成形如 \(f(i,u)\) 表示从 \(i\) 开始, 输入数对为 \((u,d_0)\), \(d_0\le f(i,u)\) 时才有合法解; 再进一步, 注意在完成 \(i\) 上的判断后, \((u,d)\) 必然有一者为 \(+a_i\) 或者 \(-a_i\), 所以状态可以简化为: \(f(i,0\sim3)\) 表示从 \(i\) 开始, 输入数对的 \(u/d\) 能够被替换为 \(+a_i/-a_i\) 时, \(d/u\) 的最大可行值. 暴力讨论 \(4^2\) 种转移对应的条件即可 \(\mathcal O(n)\) 转移.

    /*+Rainybunny+*/
    
    #include <bits/stdc++.h>
    
    #define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
    #define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
    
    inline char fgc() {
        static char buf[1 << 17], *p = buf, *q = buf;
        return p == q && (q = buf + fread(p = buf, 1, 1 << 17, stdin), p == q) ?
          EOF : *p++;
    }
    
    template <typename Tp = int>
    inline Tp rint() {
        Tp x = 0, s = fgc(), f = 1;
        for (; s < '0' || '9' < s; s = fgc()) f = s == '-' ? -f : f;
        for (; '0' <= s && s <= '9'; s = fgc()) x = x * 10 + (s ^ '0');
        return x * f;
    }
    
    template <typename Tp>
    inline void wint(Tp x) {
        if (x < 0) putchar('-'), x = -x;
        if (9 < x) wint(x / 10);
        putchar(x % 10 ^ '0');
    }
    
    template <typename Tp>
    inline void chkmin(Tp& u, const Tp& v) { v < u && (u = v, 0); }
    template <typename Tp>
    inline void chkmax(Tp& u, const Tp& v) { u < v && (u = v, 0); }
    template <typename Tp>
    inline Tp imin(const Tp& u, const Tp& v) { return u < v ? u : v; }
    template <typename Tp>
    inline Tp imax(const Tp& u, const Tp& v) { return u < v ? v : u; }
    
    const int MAXN = 1e6, IINF = 0x3f3f3f3f;
    int n, a[MAXN + 5];
    
    /**
     * 0: max available U when D=-a[i]
     * 1: max available D when U=-a[i]
     * 2: max available U when D=a[i]
     * 3: max available D when U=a[i]
     */
    int f[MAXN + 5][4];
    
    #define chkif(a, b, c) void((c) && (chkmax(a, b), 0))
    
    int main() {
        for (int T = rint(); T--;) {
            n = rint();
            rep (i, 1, n) a[i] = rint();
    
            f[n][0] = f[n][2] = IINF, f[n][1] = -a[n], f[n][3] = a[n];
            per (i, n - 1, 1) {
                rep (j, 0, 3) f[i][j] = -IINF;
        
                chkif(f[i][0], f[i + 1][0], -a[i] < -a[i + 1]);
                chkif(f[i][0], -a[i + 1], -a[i] <= f[i + 1][1]);
                chkif(f[i][0], f[i + 1][2], -a[i] < a[i + 1]);
                chkif(f[i][0], a[i + 1], -a[i] <= f[i + 1][3]);
        
                chkif(f[i][1], -a[i + 1], -a[i] <= f[i + 1][0]);
                chkif(f[i][1], f[i + 1][1], -a[i] < -a[i + 1]);
                chkif(f[i][1], a[i + 1], -a[i] <= f[i + 1][2]);
                chkif(f[i][1], f[i + 1][3], -a[i] < a[i + 1]);
                
                chkif(f[i][2], f[i + 1][0], a[i] < -a[i + 1]);
                chkif(f[i][2], -a[i + 1], a[i] <= f[i + 1][1]);
                chkif(f[i][2], f[i + 1][2], a[i] < a[i + 1]);
                chkif(f[i][2], a[i + 1], a[i] <= f[i + 1][3]);
        
                chkif(f[i][3], -a[i + 1], a[i] <= f[i + 1][0]);
                chkif(f[i][3], f[i + 1][1], a[i] < -a[i + 1]);
                chkif(f[i][3], a[i + 1], a[i] <= f[i + 1][2]);
                chkif(f[i][3], f[i + 1][3], a[i] < a[i + 1]);
        
                chkmin(f[i][1], -a[i]), chkmin(f[i][3], a[i]);
                if (f[i][0] < -a[i]) f[i][0] = -IINF;
                if (f[i][2] < a[i]) f[i][2] = -IINF;
        
                // printf("%d: %d %d %d %d\n",
                //   i, f[i][0], f[i][1], f[i][2], f[i][3]);
            }
        
            bool sol = false;
            rep (i, 0, 3) sol |= f[1][i] != -IINF;
            if (!sol) { puts("NO"); continue; }
        
            puts("YES");
            int u = -IINF, d = -IINF;
            rep (i, 1, n) {
                int x;
                if ((d <= -a[i] && u <= f[i][0])
                  || (u <= -a[i] && d <= f[i][1])) {
                    x = -a[i];
                } else {
                    x = a[i];
                }
                if (x > u) u = x;
                else d = x;
                wint(x), putchar(' ');
            }
            putchar('\n');
        }
        return 0;
    }
    
    

    \(\mathscr{B}\sim\) 最短路问题 V3

      给定含有 \(n\) 个点 \(m\) 条边的带权连通无向图, \(q\) 次询问两点最短路.

      \(n,q\le10^5\), \(m\le n+20\).


      Tag:「水题无 tag」

      拿树边之外的 \(40\) 个点暴力 Dijkstra. 复杂度 \(40\times\mathcal O(n\log n)\).

    /*+Rainybunny+*/
    
    #include <bits/stdc++.h>
    
    #define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
    #define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
    
    typedef long long LL;
    typedef std::pair<LL, int> PLI;
    #define fi first
    #define se second
    
    inline char fgc() {
        static char buf[1 << 17], *p = buf, *q = buf;
        return p == q && (q = buf + fread(p = buf, 1, 1 << 17, stdin), p == q) ?
          EOF : *p++;
    }
    
    template <typename Tp = int>
    inline Tp rint() {
        Tp x = 0, s = fgc(), f = 1;
        for (; s < '0' || '9' < s; s = fgc()) f = s == '-' ? -f : f;
        for (; '0' <= s && s <= '9'; s = fgc()) x = x * 10 + (s ^ '0');
        return x * f;
    }
    
    template <typename Tp>
    inline void wint(Tp x) {
        if (x < 0) putchar('-'), x = -x;
        if (9 < x) wint(x / 10);
        putchar(x % 10 ^ '0');
    }
    
    const int MAXN = 1e5, MAXLG = 16;
    const LL LINF = 1ll << 60;
    int n, m, ecnt = 1, head[MAXN + 5];
    int stc, dep[MAXN + 5], stn[MAXN + 5], st[MAXLG + 3][MAXN * 2 + 5];
    bool ontr[MAXN + 25];
    LL dow[MAXN + 5], dis[45][MAXN + 5];
    struct Edge { int to, cst, nxt; } graph[(MAXN + 20) * 2 + 5];
    std::vector<int> key;
    
    inline void link(const int u, const int v, const int w) {
        graph[++ecnt] = { v, w, head[u] }, head[u] = ecnt;
        graph[++ecnt] = { u, w, head[v] }, head[v] = ecnt;
    }
    
    struct DSU {
        int fa[MAXN + 5];
        inline void init() { rep (i, 1, n) fa[i] = i; }
        inline int find(const int x) {
            return x == fa[x] ? x : fa[x] = find(fa[x]);
        }
        inline bool unite(int x, int y) {
            if ((x = find(x)) == (y = find(y))) return false;
            return fa[x] = y, true;
        }
    } dsu;
    
    inline void init(const int u, const int fa) {
        st[0][stn[u] = ++stc] = u, dep[u] = dep[fa] + 1;
        for (int i = head[u], v; i; i = graph[i].nxt) {
            if (ontr[i >> 1] && (v = graph[i].to) != fa) {
                dow[v] = dow[u] + graph[i].cst, init(v, u), st[0][++stc] = u;
            }
        }
    }
    
    inline void initST() {
        rep (i, 1, 31 - __builtin_clz(stc)) {
            rep (j, 1, stc - (1 << i) + 1) {
                st[i][j] = dep[st[i - 1][j]] < dep[st[i - 1][j + (1 << i >> 1)]]
                  ? st[i - 1][j] : st[i - 1][j + (1 << i >> 1)];
            }
        }
    }
    
    inline int lca(int u, int v) {
        if ((u = stn[u]) > (v = stn[v])) std::swap(u, v);
        int k = 31 - __builtin_clz(v - u + 1);
        return dep[st[k][u]] < dep[st[k][v - (1 << k) + 1]] ?
          st[k][u] : st[k][v - (1 << k) + 1];
    }
    
    inline LL dist(const int u, const int v) {
        return dow[u] + dow[v] - 2 * dow[lca(u, v)];
    }
    
    inline void dijkstra(const int s, LL* ds) {
        std::priority_queue<PLI, std::vector<PLI>, std::greater<PLI>> heap;
        rep (i, 1, n) ds[i] = LINF;
        heap.emplace(ds[s] = 0, s);
        while (heap.size()) {
            PLI p(heap.top()); heap.pop();
            if (p.fi != ds[p.se]) continue;
            for (int i = head[p.se], v; i; i = graph[i].nxt) {
                LL d = p.fi + graph[i].cst;
                if (ds[v = graph[i].to] > d) heap.emplace(ds[v] = d, v);
            }
        }
    }
    
    int main() {
        n = rint(), m = rint(), dsu.init();
        rep (i, 1, m) {
            int u = rint(), v = rint(), w = rint();
            link(u, v, w);
            if (!(ontr[i] = dsu.unite(u, v))) key.push_back(u), key.push_back(v);
        }
    
        init(1, 0), initST();
        std::sort(key.begin(), key.end());
        key.resize(std::unique(key.begin(), key.end()) - key.begin());
        rep (i, 0, int(key.size()) - 1) dijkstra(key[i], dis[i]);
    
        for (int q = rint(); q--;) {
            int u = rint(), v = rint();
            LL ans = dist(u, v);
            rep (i, 0, int(key.size()) - 1) {
                ans = std::min(ans, dis[i][u] + dis[i][v]);
            }
            wint(ans), putchar('\n');
        }
        return 0;
    }
    

    \(\mathscr{C}\sim\) 捡石子游戏

      给定一棵含有 \(n\) 个点的树, 点 \(u\) 有点权 \(w_u\). Alice 和 Bob 博弈, Alice 选择一个起点 \(s\), 此后 Alice 和 Bob 轮流操作:

    1. \(w_s\gets w_s-1\);

    2. 取一个 \(s\) 的邻接点 \(t\), 令 \(s\gets t\). 若此时 \(w_t=0\), 则当前操作者获胜.

      求出使得 Alice 必胜的所有起点或声明无解.

      \(n\le3\times10^3\).


      Tag:「C.性质/结论」

      注意一个必败情况: Alice 陷入了一个 "山谷" \(u\), \(\forall (u,v)\in E,~w_v\ge w_u\). 那么不管 Alice 怎么挣扎, Bob 只需要在自己的回合将 \(s\) 移回 \(u\), Alice 就不可能先于 Bob 取胜.

      进一步讨论发现, 双方都不可能将 \(s\) 移动向 \(t\), 使得 \(w_s\le w_t\), 因为这样的移动肯定会被对手反向移动消除, 而 \(w_s\) 的减小更可能让自己陷入 "山谷". 换句话说, 从 \(s\) 开始的移动必然走向使得 \(w_t<w_s\)\(t\).

      这里就能直接 DP 了. 按点权升序枚举点, 检查四周能走到的点的胜负态情况. 除排序外可以做到 \(\mathcal O(n)\).

    /*+Rainybunny+*/
    
    #include <bits/stdc++.h>
    
    #define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
    #define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
    
    const int MAXN = 3e3;
    int n, val[MAXN + 5], ecnt, head[MAXN + 5], ord[MAXN + 5], sg[MAXN + 5];
    struct Edge { int to, nxt; } graph[MAXN * 2 + 5];
    
    inline void link(const int u, const int v) {
        graph[++ecnt] = { v, head[u] }, head[u] = ecnt;
        graph[++ecnt] = { u, head[v] }, head[v] = ecnt;
    }
    
    int main() {
        scanf("%d", &n);
        rep (i, 1, n) scanf("%d", &val[i]);
        rep (i, 2, n) { int u, v; scanf("%d %d", &u, &v), link(u ,v); }
    
        std::iota(ord + 1, ord + n + 1, 1);
        std::sort(ord + 1, ord + n + 1,
            [](const int u, const int v) { return val[u] < val[v]; }
        );
        
        rep (i, 1, n) {
            int u = ord[i];
            static std::bitset<MAXN + 5> vis; vis.set();
            for (int j = head[u], v; j; j = graph[j].nxt) {
                if (val[v = graph[j].to] < val[u]) {
                    vis.reset(sg[v]);
                }
            }
            sg[u] = vis._Find_first();
        }
        
        bool exi = false;
        rep (i, 1, n) if (sg[i]) {
            if (exi) putchar(' ');
            exi = true, printf("%d", i);
        }
        puts(exi ? "" : "-1");
        return 0;
    }
    

    \(\mathscr{D}\sim\) 凹函数 *

      求严格单增凹函数 \(f:[0,n]\to[0,m]\) 最多过多少个整点.

      询问次数 \(q\le10^4\), \(n,m\le3\times10^3\).


      Tags:「A.DP-杂项」「B.Tricks」

      稍作简化, 我们要求最大的一组 \(\{(x,y)_k\}\) 使得 \(x_i\perp y_i\), \(\sum x\le n,\sum y\le m\).

      直接 DP 是 \(\mathcal O(n^4)\) 左右的, 注意到凸包大小是 \(\mathcal O(n^{2/3})\) 的, 因此可以记 \(f(i,j)\) 表示 \(\sum x=i\), 向量组数为 \(j\)\(\sum y\) 的最小值. 到此可以做到 \(\mathcal O(n^{3+2/3})\).

      考虑从最优性上继续剪枝. 注意选出 \((x,y)\) 时, 一定已经选掉了所有合法的 \((x',y')<(x,y)\). 进而可以用 \(\sum x',\sum y'\) 进一步限制 \((x,y)\) 的可选性. 可以证明, 此时被用来更新背包的向量只有 \(\mathcal O(n^{2/3})\) 个, 因此最终复杂度为 \(\mathcal O(n^{7/3})\).

    Proof   设总向量数量为 $C$, 那么 $$ \begin{aligned} C &\le \sum_{x=1}^n\sum_{y=1}^n[x\perp y]\left[\sum_{x'=1}^x\sum_{y'=1}^y[x'\perp y'](x'+y')\le2n\right]\\ &\le \sum_{x=1}^n\sum_{y=1}^n\left[\sum_{x'=1}^x\sum_{y'=1}^y[x'\perp y'](x'+y')\le2n\right]\\ &\sim \sum_{x=1}^n\sum_{y=1}^n\left[\sum_{d}\mu(d)\cdot y/d\cdot d\sum_{x'=1}^{x/d}x'\le n\right]\\ &\sim \sum_{x=1}^{n}\sum_{y=1}^n\left[y\sum_{d}\mu(d)(x/d)^2\le n\right]\\ &\sim \sum_{x=1}^n\left[\sum_{y=1}^nyx^2\cdot\frac{\pi^2}{6}\le n\right]\\ &= \mathcal O(n^{2/3}). \end{aligned} $$
    /*+Rainybunny+*/
    
    #include <bits/stdc++.h>
    
    #define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
    #define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
    
    template <typename Tp>
    inline void chkmin(Tp& u, const Tp& v) { v < u && (u = v, 0); }
    template <typename Tp>
    inline void chkmax(Tp& u, const Tp& v) { u < v && (u = v, 0); }
    template <typename Tp>
    inline Tp imin(const Tp& u, const Tp& v) { return u < v ? u : v; }
    template <typename Tp>
    inline Tp imax(const Tp& u, const Tp& v) { return u < v ? v : u; }
    
    const int N = 3e3, IINF = 0x3f3f3f3f;
    std::vector<int> ans[N + 5];
    
    inline void update(const int x, const int y) {
        // printf("%d %d\n", x, y);
        per (i, N, x) {
            rep (j, 0, int(ans[i - x].size()) - 1) {
                if (ans[i - x][j] + y > N) break;
                if (ans[i].size() == j + 1) ans[i].push_back(IINF);
                chkmin(ans[i][j + 1], ans[i - x][j] + y);
            }
        }
    }
    
    inline void solve() {
        static int sum[N + 5][2];
        rep (i, 0, N) ans[i].push_back(0);
        rep (i, 1, N) {
            int s[2] = {};
            rep (j, 1, N) {
                s[0] += sum[j][0], s[1] += sum[j][1];
                if (s[0] > N || s[1] > N) break;
                if (std::__gcd(i, j) == 1) {
                    s[0] += i, s[1] += j, sum[j][0] += i, sum[j][1] += j;
                    if (s[0] > N || s[1] > N) break;
                    update(i, j);
                }
            }
        }
    }
    
    int main() {
        solve();
    
        int q; scanf("%d", &q);
        while (q--) {
            int n, m; scanf("%d %d", &n, &m);
            printf("%d\n", int(std::upper_bound(ans[n].begin(),
              ans[n].end(), m) - ans[n].begin()));
        }
        return 0;
    }
    
  • 相关阅读:
    WINDOWSXP文件夹右键属性没有“安全”选项卡的解决
    无法为类型 CuteEditor.Editor 授予有效的许可证。
    DSO Framer _ WebForm 使用
    sql语句中日期时间格式化查询
    url 编码 中文|c# js url传参中文乱码解决方案
    UML中类图实例(转载)
    JavaSE重点——注解和反射
    JavaSE重点——内部类(转载)
    JavaSE重点——Java8新特性
    JavaSE重点——网络编程
  • 原文地址:https://www.cnblogs.com/rainybunny/p/16750843.html
Copyright © 2020-2023  润新知