zoukankan      html  css  js  c++  java
  • [NOI2020]命运

    显然直接计数是不好计的,只能从 (dp) 这个角度来下手。

    首先用最原始最直接的方法,直接在 (dp) 的过程中满足题目的要求。

    既然问题给在一棵树上,那么必然和树脱不了关系,因此我们应该从树形 (dp) 的角度下手。

    因为在树形 (dp) 的过程中我们只能考虑父子边的选择情况,那么对于一个点 (u),那些限制链顶在 (u) 子树内的限制链显然在其父亲 (fa) 进行转移时是考虑不到的,因此设计状态的时候必然要使得这些链已经被满足。

    那么再来考虑这些链底在 (u) 子树内,链顶在 (u) 的祖先上的这些链。

    不难发现只需要满足的链是链顶深度最深的这条链,因为只要满足了它其他的链也会被满足。

    所以我们在 (dp) 的时候只关注于当前伸出去的链中链顶最深的链,于是我们的 (dp) 状态就呼之欲出了。

    考虑令 (dp_{i, j}) 表示以 (i) 为根的子树内,还没有被满足限制的链中链顶伸出去的链的最深的链顶深度为 (j) 时的方案,特别地 (j = 0) 时表示没有伸上来的链。

    那么转移的时候只要考虑 (u ightarrow v) 这条父子边选 (0 / 1) 即可。

    (1) 时,(v) 伸上来的所有链都会被满足,于是有转移:

    [dp_{u, i} imes sumlimits dp_{v, j} ightarrow dp_{u, i} ]

    (0) 时,只需要考虑当前这个最深的链顶是否来自于 (v) 即可。

    不来自于 (v) :

    [dp_{u, i} imes sumlimits_{j = 0} ^ i dp_{v, j} ightarrow dp_{u, i} ]

    来自于 (v)

    [dp_{v, i} sumlimits_{j = 0} ^ i dp_{u, j} ightarrow dp_{u, i} ]

    需要注意深度相同时 (dp_{u, i} imes dp_{v, i} ightarrow dp_{u, i}) 被计算了两次,需要减去。

    但这样依然不能通过本题,怎么办呢?

    可以发现,这个 (dp) 的状态量就惊人地达到了 (O(n ^ 2)),因此我们必须要从状态这里下手。

    这时候我们引入一个叫做整体 (dp) 的技巧,其适用范围往往是存在一个维度上初始值并不多的情况。

    其做法就是将这一个需要优化的维度放到动态开点线段树上,然后可以通过主席树或线段树合并一类技巧来优化这个 (dp) 的流程。

    那么在本题当中,我们可以将第二维放到动态开点线段树上。

    那么对于第一条转移,相当于是给 (u) 这个线段树上每个位置乘上 (v) 中所有数之和,不难发现这可以直接在线段树上完成。

    对于第二条转移,相当于是对于 (u) 所在线段树上每个非 (0) 的点乘上 (v) 所在线段树上 (u) 左侧所有位置之和,不难发现这个操作是可以在线段树合并的时候同时完成的,只需要递归时记录当前区间左侧所有数之和即可。

    对于第三条转移,本质上与第二条转移没有区别。

    因此这个转移的过程就被我们在线段树合并的同时优化掉了,复杂度 (O(n log n))

    #include <bits/stdc++.h>
    using namespace std;
    #define ls t[p].l
    #define rs t[p].r
    #define mid (l + r >> 1)
    #define rep(i, l, r) for (int i = l; i <= r; ++i)
    #define Next(i, u) for (int i = h[u]; i; i = e[i].next)
    const int N = 500000 + 5;
    const int Mod = 998244353;
    struct tree { int sum, tag, l, r;} t[N * 40];
    struct edge { int v, next;} e[N << 1];
    int n, m, u, v, tot, cnt, h[N], rt[N], dep[N], val[N];
    int read() {
        char c; int x = 0, f = 1;
        c = getchar();
        while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
        while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    int Inc(int a, int b) { return (a += b) >= Mod ? a - Mod : a;}
    int Dec(int a, int b) { return (a -= b) < 0 ? a + Mod : a;}
    int Mul(int a, int b) { return 1ll * a * b % Mod;}
    void add(int u, int v) {
        e[++tot].v = v, e[tot].next = h[u], h[u] = tot;
        e[++tot].v = u, e[tot].next = h[v], h[v] = tot;
    }
    void Prefix(int u, int fa) {
        dep[u] = dep[fa] + 1;
        Next(i, u) if(e[i].v != fa) Prefix(e[i].v, u);
    }
    void down(int p) {
        t[ls].sum = Mul(t[ls].sum, t[p].tag), t[rs].sum = Mul(t[rs].sum, t[p].tag);
        t[ls].tag = Mul(t[ls].tag, t[p].tag), t[rs].tag = Mul(t[rs].tag, t[p].tag);
        t[p].tag = 1;
    }
    void update(int &p, int l, int r, int x, int y, int k) {
        if(!p) p = ++cnt, t[p].tag = 1; t[p].sum += k;
        if(l == r) return;
        if(mid >= x) update(ls, l, mid, x, y, k);
        if(mid < y) update(rs, mid + 1, r, x, y, k);
    }
    void modify(int &p, int l, int r, int x, int y) {
        if(!p) p = ++cnt, t[p].tag = 1;
        if(l == r) { t[p].sum = 0; return;}
        down(p);
        if(mid >= x) modify(ls, l, mid, x, y);
        if(mid < y) modify(rs, mid + 1, r, x, y);
        t[p].sum = Inc(t[ls].sum, t[rs].sum);
    }
    void Merge(int &p, int k, int l, int r, int S1, int S2, int S) {
        if(!p || !k) {
            if(!p && !k) return;
            if(!p) p = p + k, t[p].sum = Mul(t[k].sum, S1), t[p].tag = Mul(t[p].tag, S1);
            else t[p].sum = Mul(t[p].sum, Inc(S2, S)), t[p].tag = Mul(t[p].tag, Inc(S2, S));
            return ;
        }
        if(l == r) { 
            S1 = Inc(S1, t[p].sum), S2 = Inc(S2, t[k].sum);
            int tmp = Mul(t[p].sum, t[k].sum);
            t[p].sum = Inc(Mul(t[k].sum, S1), Mul(t[p].sum, Inc(S, S2))); 
            t[p].sum = Dec(t[p].sum, tmp);
            return ;
        }
        down(p), down(k);
        Merge(rs, t[k].r, mid + 1, r, Inc(S1, t[ls].sum), Inc(S2, t[t[k].l].sum), S);
        Merge(ls, t[k].l, l, mid, S1, S2, S);
        t[p].sum = Inc(t[ls].sum, t[rs].sum);
    }
    int query(int p, int l, int r, int x, int y) {
        if(!p) return 0;
        if(l >= x && r <= y) return t[p].sum;
        down(p);
        int ans = 0;
        if(mid >= x) ans = Inc(ans, query(ls, l, mid, x, y));
        if(mid < y) ans = Inc(ans, query(rs, mid + 1, r, x, y));
        return ans;
    }
    void dfs(int u, int fa) {
        update(rt[u], 0, n, val[u], val[u], 1);
        Next(i, u) { 
            int v = e[i].v; if(v == fa) continue;
            dfs(v, u), Merge(rt[u], rt[v], 0, n, 0, 0, t[rt[v]].sum);
        }
        modify(rt[u], 0, n, dep[u], dep[u]);
    }
    int main() {
        n = read();
        rep(i, 1, n - 1) u = read(), v = read(), add(u, v);
        Prefix(1, 0);
        m = read();
        rep(i, 1, m) u = read(), v = read(), val[v] = max(val[v], dep[u]);
        dfs(1, 0);
        printf("%d", t[rt[1]].sum);
        return 0;
    }
    

    (dp) 状态已经超过要求后,如果存在某一维初始值比较少,整体 (dp) 不失一个好的选择。

    GO!
  • 相关阅读:
    密码学复习
    Kafka Stream 高级应用
    Kafka Stream 流和状态
    Kafka Stream 处理器API
    SSM工作流程与原理详解
    Seata AT和XA模式
    分布式锁结合SpringCache
    使用RabbitMQ最终一致性库存解锁
    使用Springboot+SpringCloud+Seata1.3.0+Nacos1.2.1进行全局事务管理
    在微服务环境下,远程调用feign和异步线程存在请求数据丢失问题
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13819753.html
Copyright © 2011-2022 走看看