• 【洛谷】P2986 [USACO10MAR]Great Cow Gathering G【树上DP】【换根DP/找重心】


    多亏了队里巨巨让我理解了第二种解法orzorz能遇到佬佬们的我真是太幸运辣!!

    P2986 [USACO10MAR]Great Cow Gathering G

    第一眼就觉得是找重心,可是发现权值是根据所选点动态变化的,然后懵了一会儿想到了第一种解法。

    既然有点权和边权那就分开处理,首先用$siz[u]$处理出每个子树总的$c$值,再假设$1$号节点为所求,先统计一遍总不方便值$f[1]$,再跑一遍$dfs$,很容易发现此时往下$DP$时以$v$点为根的不方便值就应该是$f[v]=f[u]-siz[v]*w+(sum-siz[v])*w$,直接找出最优即可。

    #include<bits/stdc++.h>
    
    #define ll long long
    
    const ll oo = 1e55;
    const int N = 100005;
    
    using namespace std;
    
    int n;
    ll c[N];
    
    struct Node {
        int v, nex; ll w;
        Node(int v = 0, int nex = 0, ll w = 0) :
            v(v), nex(nex), w(w) { }
    } Edge[N << 1];
    
    int stot, h[N];
    void add(int u, int v, ll w) {
        Edge[++stot] = Node(v, h[u], w);
        h[u] = stot;
    }
    
    ll siz[N], d[N], f[N];
    void dfs1(int u, int fa) {
        siz[u] = c[u];
        for(int i = h[u]; i; i = Edge[i].nex) {
            int v = Edge[i].v;
            if(v == fa)    continue;
            dfs1(v, u);
            siz[u] += siz[v];
            f[u] += f[v];
            f[u] += siz[v] * Edge[i].w;
        }
    }
    
    ll sum;
    void dfs2(int u, int fa) {
        for(int i = h[u]; i; i = Edge[i].nex) {
            int v = Edge[i].v;
            if(v == fa)    continue;
            f[v] = f[u] + (sum - siz[v]) * Edge[i].w - siz[v] * Edge[i].w;
            dfs2(v, u);
        }
    }
    
    int main() {
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &c[i]);    sum += c[i];
        }
        for(int i = 1; i < n; i ++) {
            int u, v; ll w;
            scanf("%d%d%lld", &u, &v, &w);
            add(u, v, w);
            add(v, u, w);
        }
        dfs1(1, 0);
        dfs2(1, 0);
        ll ans = oo;
        for(int i = 1; i <= n; i ++)
            ans = min(ans, f[i]);
        printf("%lld", ans);
        return 0;
    }

    第二种方法找带权树重心然后换根DP。

    发现了对于每一条连接$u$和$v$的边,取$u$和取$v$为根对答案贡献的区别就是是用$u$子树的$siz$乘这条边权还是用$v$的$siz$。画画图就发现只有当带权树重心为根时能保证每条边的贡献最小。这个权值就应该是$siz$。

    于是跑出重心来直接用每个点到重心距离乘$c$即可。(注意这里不用$siz$,因为只用分别计当前点的贡献即可)

    (强迫症又专门写了一遍自己的代码风格)


    #include<bits/stdc++.h>
    
    #define ll long long
    
    const ll oo = 1e55;
    const int N = 100005;
    
    using namespace std;
    
    int n;
    ll c[N];
    
    struct Node {
        int v, nex; ll w;
        Node(int v = 0, int nex = 0, ll w = 0) :
            v(v), nex(nex), w(w) { }
    } Edge[N << 1];
    
    int stot, h[N];
    void add(int u, int v, ll w) {
        Edge[++stot] = Node(v, h[u], w);
        h[u] = stot;
    }
    
    ll d[N], f[N], res = 0x3f3f3f3f, rt;
    int siz[N], sum;
    void dfs1(int u, int fa) {
        int maxr = 0;
        siz[u] = c[u];
        for(int i = h[u]; i; i = Edge[i].nex) {
            int v = Edge[i].v;
            if(v == fa)    continue;
            dfs1(v, u);
            siz[u] += siz[v];
            maxr = max(maxr, siz[v]);
        }
        maxr = max(maxr, sum - siz[u]);
        if(maxr < res)    res = maxr, rt = u;
    }
    
    ll dis[N];
    void dfs2(int u, int fa) {
        for(int i = h[u]; i; i = Edge[i].nex) {
            int v = Edge[i].v;
            if(v == fa)    continue;
            dis[v] = dis[u] + Edge[i].w;
            dfs2(v, u);
        }
    }
    
    int main() {
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &c[i]);    sum += c[i];
        }
        for(int i = 1; i < n; i ++) {
            int u, v; ll w;
            scanf("%d%d%lld", &u, &v, &w);
            add(u, v, w);
            add(v, u, w);
        }
        dfs1(1, 0);
        dfs2(rt, 0);
        ll ans = 0;
        for(int i = 1; i <= n; i ++)
            ans += 1ll * c[i] * dis[i];
        printf("%lld", ans);
        return 0;
    }

    呱呱呱呱呱呱呱呱呱呱呱呱呱呱呱(?)

  • 相关阅读:
    C#验证类(使用正则表达式)
    SQL数据库还原语句
    JS键盘或鼠标事件
    列表针对列宽度自动调整显示内容,超长以...缩写结尾
    SQL按照日、周、月、年统计数据 (转自BLEACH的blog)
    SQL利用Case When Then多条件判断
    调用Web服务:请求因HTTP状态401失败:Unauthorized
    IFrame自动适应宽高,去掉空白
    Asp调用WebService事例
    Timer不执行Elapsed事件的解决办法
  • 原文地址:https://www.cnblogs.com/wans-caesar-02111007/p/13945872.html
Copyright © 2020-2023  润新知