【洛谷 P2726】 [SHOI2005]树的双中心(树的重心)

先考虑一个\(O(N^2)\)做法。

设选的两个点为\(x,y\),则一定可以将树分成两个集合\(A,B\),使得\(A\)集合所有点都去\(x\)\(B\)集合所有点都去\(y\),而这两个集合的分界点就是树上的一条边。于是考虑枚举断哪条边,然后对两边分别跑一遍带权树的重心,统计答案加起来取最小值就行了。

现在进行优化,求树的重心的方法可以参考医院设置
\(1\)为根建树,\(f[u]\)表示所有点到\(u\)的总距离。(人数乘以距离)

转移方程是:
\[f[v]=f[u]+size[1]-size[v]-size[v]\]
可以发现,只有当\(size[v]*2>size[1]\)\(v\)\(u\)更优,而且满足\(size[v]*2>size[1]\)\(v\)数量\(<=1\)

所以我们可以预处理出每个点的子树大小最大的儿子和次大的儿子,每次断边时自下而上修改其所有祖先的\(size\)大小,这时最大儿子可能变小,进而被次大儿子替代,直接判断一下然后走此时的大儿子就行。易得时间复杂度为\(O(NH)\),这也就是题中提到树的高度不超过\(100\)的原因吧。

#include <cstdio>
#include <algorithm>
#define INF 2147483647
using namespace std;
#define Open(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout);
#define Close fclose(stdin);fclose(stdout);
int s, w; char ch;
inline int read(){
    s = 0; ch = getchar();
    while(ch < '0' || ch > '9') ch = getchar(); 
    while(ch >= '0' && ch <= '9'){ s = s * 10 + ch - '0'; ch = getchar();}
    return s;
}
const int MAXN = 100010;
struct Edge{
    int next, to;
}e[MAXN << 1];
int head[MAXN], num, a[MAXN], size[MAXN], f[MAXN], val[MAXN], dep[MAXN], son[MAXN], Sson[MAXN], A, B, n, root, ans = INF, cut;
inline void Add(int from, int to){
    e[++num].to = to; e[num].next = head[from]; head[from] = num;
    e[++num].to = from; e[num].next = head[to]; head[to] = num;
}
int getsize(int u, int fa){
    size[u] = a[u]; f[u] = fa; dep[u] = dep[fa] + 1;
    for(int i = head[u]; i; i = e[i].next)
        if(e[i].to != fa){
            getsize(e[i].to, u);
            size[u] += size[e[i].to];
            val[u] += val[e[i].to] + size[e[i].to];
            if(size[e[i].to] > size[son[u]]){
              Sson[u] = son[u];
              son[u] = e[i].to;
            }
            else if(size[e[i].to] > size[Sson[u]])
              Sson[u] = e[i].to;
        }
}
void getans(int u, int now, int all, int &res){
    res = min(res, now);
    int v = son[u];
    if(v == cut || size[Sson[u]] > size[son[u]]) v = Sson[u];   //如果size变化后次大大于最大
    if(!v) return;
    if(size[v] * 2 > all) getans(v, now + all - size[v] - size[v], all, res);  //如果size[v]*2<=all就没有继续往下走的意义了,因为此时u一定最优
}
int solve(int u){
    for(int i = head[u]; i; i = e[i].next)
       if(e[i].to != f[u]){
         cut = e[i].to;  //断边
         A = B = INF;
         for(int now = u; now; now = f[now]) size[now] -= size[e[i].to];  //自下而上修改其父亲size
         getans(1, val[1] - val[e[i].to] - dep[e[i].to] * size[e[i].to], size[1], A);
         getans(e[i].to, val[e[i].to], size[e[i].to], B);    //求两个集合的答案
         ans = min(ans, A + B);
         for(int now = u; now; now = f[now]) size[now] += size[e[i].to];   //回溯
         solve(e[i].to);
       }
}
int main(){
    //Open("practice");
    n = read(); dep[0] = -1;
    for(int i = 1; i < n; ++i) Add(read(), read());
    for(int i = 1; i <= n; ++i) a[i] = read();
    getsize(1, 0);
    solve(1);
    printf("%d\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/Qihoo360/p/9871320.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值