20240625

T1

洛谷 P3267 侦查守卫

考虑对一个子树,我们关心什么。我们关心的是其中最深的未覆盖关键点的深度以及最浅守卫的深度。如果把这两个都扔到状态里,空间爆炸。发现如果一个子树还有关键点没有覆盖,那关心其中最浅守卫的深度就是无意义的,因为反正还要再放一个,而且新放的一定比子树中的浅。所以只需要在子树合法时记录最浅守卫深度,不合法时记录最深未覆盖点深度即可。设 \(f[i][j]\) 表示 \(i\) 子树合法的情况下向上最多还能盖 \(j\) 条边,\(g[i][j]\) 表示 \(i\) 子树最深的未覆盖点到 \(i\) 的边数 \(+1\),两种情况的最小代价。转移就每次加入一棵子树。注意一轮更新结束后 \(f\) 要做后缀最小值,\(g\) 要做前缀最小值,因为如果更优的状态有着更优的代价,那当前这个状态就可以直接把那个更优的代价拿过来。

代码
#include <iostream>
#include <string.h>
using namespace std;
bool bg;
const int inf = 0x3f3f3f3f;
int n, m, d;
int w[500005];
bool key[500005];
int head[500005], nxt[1000005], to[1000005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int f[500005][25];
int g[500005][25];
void dfs(int x, int fa) {
    if (key[x]) 
        f[x][0] = g[x][0] = w[x];
    for (int i = 1; i <= d; i++) f[x][i] = w[x];
    f[x][d + 1] = inf;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) {
            dfs(v, x);
            for (int j = d; ~j; j--) {
                f[x][j] = min(f[x][j] + g[v][j], g[x][j + 1] + f[v][j + 1]);
                f[x][j] = min(f[x][j], f[x][j + 1]);
            }
            g[x][0] = f[x][0];
            for (int j = 1; j <= d + 1; j++) {
                g[x][j] += g[v][j - 1];
                g[x][j] = min(g[x][j], g[x][j - 1]);
            }
        }
    }
}
bool ed;
int main() {
    cerr << (&ed - &bg) / 1024.0 / 1024.0 << "\n";
    freopen("observer.in", "r", stdin);
    freopen("observer.out", "w", stdout);
    cin >> n >> d;
    for (int i = 1; i <= n; i++) cin >> w[i];
    cin >> m;
    for (int i = 1, x; i <= m; i++) cin >> x, key[x] = 1;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    dfs(1, 0);
    cout << f[1][0] << "\n";
    return 0;
}

T2

NFLSOJ P5022 Imbalance

考虑没有限制时怎么做。显然可以异或前缀和,然后 01 trie 维护。这应当是平凡的。

接下来考虑限制。容易想到对每种值前缀和,然后两种数 \(a, b\)\([l, r]\) 出现次数相同当且仅当 \(S_b[r] - S_b[l - 1] = S_a[r] - S_a[l - 1]\)。移项,得 \(S_b[r] - S_a[r] = S_b[l - 1] - S_a[l - 1]\)。这样两边都只分别与 \(i, j\) 有关。对于每个 \(i\),我们算出 \(S_a[i] - S_b[i], S_b[i] - S_c[i], S_a[i] - S_c[i]\) 三个值。这样区间 \([l, r]\) 符合条件当且仅当 \(l - 1\) 的三元组与 \(r\) 的三元组有至少一个数相等。

注意到“至少一个”,我们可以想到容斥。初步地,我们将每一个三元组 \(\{ a, b, c \}\) 拆成 \(\{ a, b, c \}, \{ a, b, -1 \}, \{ a, -1, b \}\) 等等这样 \(8\) 个三元组,对每个三元组建立一颗 01 trie,每次插入时把插入的数分别插入这 \(8\) 个对应的 trie 中,查询时我们使用容斥,把查询的位置的三元组对应的 \(8\) 个 trie 的根同时扔到 trie 里查。这样时空复杂度单 \(\log\),只是都有 \(8\) 倍常数。

过不去,考虑优化。能够发现若两个这样的三元组中有两个数相同,这两个三元组必然全等(结合第一段讲的判断条件理解)。于是可以发现查询时有非常多冗余,比如说 \(\{ a, b, -1 \}\)\(\{ a, -1, c \}\)\(\{ -1, b, c \}\)\(\{ a, b, c \}\) 的 trie 必然全等。于是可以省去“强制两个数相等”情况的两种插入与“三种全部相等”的插入,查询时也相应简化。这样就只有 \(5\) 倍常数了,可以通过。

代码
#include <iostream>
#include <map>
using namespace std;
const int inf = 2147483647;
inline char nnc(){
    static char buf[1000005],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000005,stdin),p1==p2)?EOF:*p1++;
}
inline int read() {
    int ret = 0;
    char c = nnc();
    while (!isdigit(c) && !isalpha(c)) c = nnc();
    while (isdigit(c) || isalpha(c)) ret = ret * 10 + c - 48, c = nnc();
    return ret;
}
bool bg;
int n, opt;
struct node {
    int a, b, c;
};
bool operator<(node a, node b) { return (a.a == b.a ? (a.b == b.b ? (a.c < b.c) : (a.b < b.b)) : (a.a < b.a)); }
int ncnt = 1;
struct Trie {
    const int MX = 29;
    int T[46000005][2];
    int cnt[46000005];
    void Insert(int rt, int x) {
        int p = rt;
        for (int i = MX; ~i; i--) {
            int t = (x >> i) & 1;
            T[p][t] ? 0 : (T[p][t] = ++ncnt);
            p = T[p][t];
            cnt[p]++;
        }
    }
    int Query(int a, int b, int c, int d, int e, int x) {
        int ret = 0;
        for (int i = MX; ~i; i--) {
            int t = !((x >> i) & 1);
            if (cnt[T[a][t]] - cnt[T[b][t]] - cnt[T[c][t]] - cnt[T[d][t]] + cnt[T[e][t]] * 2) {
                ret |= (1 << i);
                a = T[a][t], b = T[b][t], c = T[c][t], d = T[d][t], e = T[e][t];
            } else 
                a = T[a][!t], b = T[b][!t], c = T[c][!t], d = T[d][!t], e = T[e][!t];
        }
        return ret;
    }
} Trie;
map<node, int> rt;
int p[300005], A[300005];
int mp(node a) {
    if (!rt.count(a)) 
        return rt[a] = ++ncnt;
    else 
        return rt[a];
}
void Add(node x, int y) {
    node a;
    int t = 1;
    Trie.Insert(t, y);
    a = (node) { x.a, inf, inf };
    Trie.Insert(mp(a), y);
    a = (node) { inf, x.b, inf };
    Trie.Insert(mp(a), y);
    a = (node) { inf, inf, x.c };
    Trie.Insert(mp(a), y);
    a = (node) { x.a, x.b, inf };
    Trie.Insert(mp(a), y);
}
int Query(node x, int y) {
    int b, c, d, e;
    b = mp((node) { x.a, inf, inf });
    c = mp((node) { inf, x.b, inf });
    d = mp((node) { inf, inf, x.c });
    e = mp((node) { x.a, x.b, inf });
    return Trie.Query(1, b, c, d, e, y);   
}
int pre[300005];
bool ed;
signed main() {
    // cerr << (&ed - &bg) / 1024.0 / 1024.0 << "\n";
    freopen("imbalance.in", "r", stdin);
    freopen("imbalance.out", "w", stdout);
    n = read(), opt = read();
    for (int i = 1; i <= n; i++) p[i] = read();
    for (int i = 1; i <= n; i++) A[i] = read();
    int lans = 0;
    Add((node) { 0, 0, 0 }, 0);
    int a, b, c;
    a = b = c = 0;
    for (int i = 1; i <= n; i++) {
        if (opt) {
            p[i] = (p[i] ^ lans) % 3;
            A[i] ^= lans;
        }
        pre[i] = pre[i - 1] ^ A[i];
        p[i] == 0 ? (++a) : (p[i] == 1 ? ++b : ++c);
        int x = a - b, y = a - c, z = b - c;
        cout << (lans = Query((node) { x, y, z }, pre[i])) << " ";
        Add((node) { x, y, z }, pre[i]);
    }
    cout << "\n";
    return 0;
}

T3

摆。


根据性质缩减冗余状态。

posted @ 2024-06-25 22:49  forgotmyhandle  阅读(19)  评论(0)    收藏  举报