Live2D

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;
}
posted @ 2022-10-03 17:31  Rainybunny  阅读(79)  评论(0编辑  收藏  举报