zoukankan      html  css  js  c++  java
  • 树dp...吧 ZOJ 3949

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5568

    Edge to the Root

    Time Limit: 1 Second      Memory Limit: 131072 KB

    Given a tree with n vertices, we want to add an edge between vertex 1 and vertex x, so that the sum of d(1, v) for all vertices v in the tree is minimized, where d(uv) is the minimum number of edges needed to pass from vertex u to vertex v. Do you know which vertex x we should choose?

    Recall that a tree is an undirected connected graph with n vertices and n - 1 edges.

    Input

    There are multiple test cases. The first line of input contains an integer T, indicating the number of test cases. For each test case:

    The first line contains an integer n (1 ≤ n ≤ 2 × 105), indicating the number of vertices in the tree.

    Each of the following n - 1 lines contains two integers u and v (1 ≤ uv ≤ n), indicating that there is an edge between vertex u and v in the tree.

    It is guaranteed that the given graph is a tree, and the sum of n over all test cases does not exceed 5 × 105. As the stack space of the online judge system is not very large, the maximum depth of the input tree is limited to about 3 × 104.

    We kindly remind you that this problem contains large I/O file, so it's recommended to use a faster I/O method. For example, you can use scanf/printf instead of cin/cout in C++.

    Output

    For each test case, output a single integer indicating the minimum sum of d(1, v) for all vertices v in the tree (NOT the vertex x you choose).

    Sample Input

    2
    6
    1 2
    2 3
    3 4
    3 5
    3 6
    3
    1 2
    2 3
    

    Sample Output

    8
    2
    

    Hint

    For the first test case, if we choose x = 3, we will have

    d(1, 1) + d(1, 2) + d(1, 3) + d(1, 4) + d(1, 5) + d(1, 6) = 0 + 1 + 1 + 2 + 2 + 2 = 8

    It's easy to prove that this is the smallest sum we can achieve.


    Author: WENG, Caizhi
    Source: The 17th Zhejiang University Programming Contest Sponsored by TuSimple

    题目大意:给你一棵树,这棵树的每条边的length都是1。然后这棵树是以1为root,定义他的weight就是所有的节点的deep和。

    现在你有一个操作,对于任意的u,可以从(1,u)连接一条边,问选择哪个节点u连接可以使得树的weight最小。

    思路:其实很早就有思路了...但是最近心情太烦了,写的时候都很烦,就没有做。

    刚开始sb的用线段树啊,树状数组啊来维护,发现好烦。然后第二天删光了所有的代码重新来,但还是摆脱不了树状数组...(感觉就算写对了应该也是TLE了)

    今天突然发现可以维护一下前缀就好了(真的是......)

    首先我们可以发现,对于(1,u)连接了边以后,那么深度为deep[u]/2 + 1的这些点的deep都会发生改变。

    我们从1开始进行dfs,然后我们对于经过的所有的节点,都把他定义为deep = 1.然后定义前面一个子树的区间为[l, r],当前的区间为[L, R],然后利用这个进行修改即可

    具体的就是容斥一下就好了,还不懂看着代码里面的注释,然后自己画一画

    //看看会不会爆int!数组会不会少了一维!
    //取物问题一定要小心先手胜利的条件
    #include <bits/stdc++.h>
    using namespace std;
    #pragma comment(linker,"/STACK:102400000,102400000")
    #define LL long long
    #define ALL(a) a.begin(), a.end()
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    #define haha printf("haha
    ")
    const int maxn = 2e5 + 5;
    int n;
    vector<int> G[maxn];
    int deep[maxn], sz[maxn];
    LL cnt[maxn];
    
    void dfs_sz(int u, int fa, int d){
        deep[u] = d; sz[u] = 1; cnt[u] = d;
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa) continue;
            dfs_sz(v, u, d + 1);
            sz[u] += sz[v];
            cnt[u] += cnt[v];
        }
    }
    
    LL ans;
    LL rest, res, addval;
    ///rest表示每次需要加回来的东西是多少,addval表示rest的和
    ///res表示把路上经过的点deep都变成1所剩下的val
    LL pre[maxn];//表示深度为l的有多少节点是被修改了的
    void dfs_solve(int u, int fa, int l, int r){
        pre[deep[u]] = sz[u];
        int L = deep[u] / 2 + 1, R = deep[u];
        if (R > r) res -= sz[u] * (deep[u] - 1);///减去子树的
        if (L > l) rest -= pre[l];///减去之前子树的size
        addval += rest;
        ans = min(ans, addval + res);
        for (int i = 0; i < G[u].size(); i++){
            int v = G[u][i];
            if (v == fa) continue;
            pre[deep[u]] -= sz[v];
            rest += pre[deep[u]];
            res += (deep[u] - 1) * sz[v];
            dfs_solve(v, u, L, R);
            res -= (deep[u] - 1) * sz[v];
            rest -= pre[deep[u]];
            pre[deep[u]] += sz[v];
        }
        if (R > r) res += sz[u] * (deep[u] - 1);
        addval -= rest;
        if (L > l) rest += pre[l];
    }
    
    int main(){
        int t; cin >> t;
        while (t--){
            scanf("%d", &n);
            for (int i = 1; i <= n; i++) G[i].clear();
            for (int i = 1; i < n; i++){
                int u, v; scanf("%d%d", &u, &v);
                G[u].pb(v), G[v].pb(u);
            }
            dfs_sz(1, 0, 0);
            res = ans = cnt[1];
            addval = 0;
            for (int i = 0; i < G[1].size(); i++){
                int v = G[1][i];
                dfs_solve(v, 1, 1, 0);
            }
            printf("%lld
    ", ans);
        }
        return 0;
    }
    /*
    456
    7
    1 2
    2 3
    3 4
    3 5
    5 7
    4 6
    
    ans = 12
    */
    View Code
  • 相关阅读:
    leetcode 买卖股票的最佳时机3
    leetcode 买卖股票的最佳时机Ⅱ
    leetcode 最长有效括号
    C++中的%lld和%I64d区别
    Ural 1095 Nikifor 3 思维+同余性质的利用
    博弈基础
    ural 1091. Tmutarakan Exams
    容斥原理
    一些易错的地方
    codeforces911D Inversion Counting 求逆序数+小trick
  • 原文地址:https://www.cnblogs.com/heimao5027/p/6714429.html
Copyright © 2011-2022 走看看