树的重心

树的重心

所谓重心是在树中删除后使得剩下的连通块节点数最大的最小的点

重心的性质:

  1. 树上所有点到一点的距离(可带权)之和是到所有点距离之和中最短的
  2. 连接两颗树其新重心在于两树的重心的路径上
  3. 树增加或删除一个叶子,重心最多移动一个节点
  4. 树的重心若不唯一最多为2,且两个重心相邻
  5. 以重心为根的子树的大小不会超过总节点数的一半

求重心

#include <bits/stdc++.h>
#define MAXN (int)(2e5 + 5)
using namespace std;
//vis标记是否访问过该点,由于是数的结构所以入度最多只有1,因此不会有两个点移到同一个点的情况,所以不会有一个点访问过,另一个点子树就“断了”的情况,vis主要是防止访问来源的节点
//zishu[x]保存删掉该节点的剩余子树最大节点数
int head[MAXN], n, vis[MAXN], countn, ans = 0x7f7f7f7f,mx=-1,zishu[MAXN];
struct s
{
    int to, next;
} edge[MAXN];
//链式前向星建树
void lsqxx(int from, int to)
{
    edge[++countn].to = to;
    edge[countn].next = head[from];
    head[from] = countn;
}
//树的深度优先遍历求重心
int dfs(int x)
{
    int now = 0;
    int sum = 1;
    vis[x] = 1;
    for (int i = head[x]; i != -1; i = edge[i].next)
    {
        if (!vis[edge[i].to])
        {
            int nex = dfs(edge[i].to);//返回的nex是以下一节点为根节点子树的
        zishu[x] = max(zishu[x], nex);//
            sum += nex;
        }
    }
    zishu[x]  = max(zishu[x] , n - sum);
    
    if(ans>zishu[x] )
    ans = zishu[x] ,mx=x;
    
    return sum;
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d", &n);
    for (int i = 1; i < n; i++)
    {
        int from, to;
        scanf("%d%d", &from, &to);
        lsqxx(from, to);
        lsqxx(to, from);
    }
    dfs(1);
    cout << ans << endl;
    // cout<<mx<<endl;
    return 0;
}

luoguP2986

重心的应用

本题两种解法,本质是一种

1.找出树的重心并算出不方便值,将带权点拆为看做数个点,然后由于所有点到重心距离最小,此时的距离之和就是不方便值,求出重心然后树dfs求出距离

2.找出每个节点的子树的子节点点权之和在树的dfs过程中计算节点1的总不方便值f[1]

\[\sum_{i=1}^{n}(p_i*dis_i) \]

其中pi是点权dis是边权

靠转移方程求每个点的总不方便值

f[x]指的是到x点总不方便值,zishu[x]值以x为根的子树子树点权之和为的是之后计算f [x] 转移方程式,以x为根的子树可能有很多个,这里只记录了一个dfs方向上的子树点权之和

\[f[u]=f[x]+(zishu[1]-zishu[nex])*dis_{x->u}-(zishu[nex])*dis_{x->u} \]

\[-(zishu[nex])*dis_{x->u} \]

这个部分的意思是从x移动到u位置的时候,

以总不方便值会相对于f[x]减少(u为根子树的点权之和由x到u的距离),

\[+(zishu[1]-zishu[nex])*dis_{x->u} \]

而剩余部分则会因为移动而增加 (剩余子树的点权之和由x到u的距离),(zishu[1]-zishu[u])是剩余部分

//O(n)
#include <bits/stdc++.h>
#define MAXN (int)1e5 + 5
using namespace std;
long long countn, zishu[MAXN];
long long qz[MAXN], head[MAXN];
bool vis[MAXN];
long long f[MAXN], minn = 1e18;

struct s
{
    long long to, next, length;
} edge[2 * MAXN];
void add(long long from, long long to, long long length)
{
    edge[++countn].to = to;
    edge[countn].next = head[from];
    edge[countn].length = length;
    head[from] = countn;
}
long long dfs(long long id, long long x)
{
    zishu[x] = qz[x];
    vis[x] = 1;
    for (long long i = head[x]; i != 0; i = edge[i].next)
    {
        if (!vis[edge[i].to])
        {
            zishu[x] += dfs(i, edge[i].to);
        }
    }
    // cout << zishu[x] << " " << x << endl;
    // cout << edge[id].length << endl;
    f[1] += zishu[x] * edge[id].length;
    
    //此处在每个子树上的节点权值和*当前所来使的距离并加到f[1]中,相当于将该点移动到了来时所来的点,每次遍历点都会求一次zishu[x]和+zishu[x]*edge[id].length,就可以求出f[1]点的不方便值,每次移动一段距离都会加上移动到该节点子树的(节点权值和*距离)
    
    return zishu[x];
}
void dp(long long last, long long x)
{
    vis[x] = 1;

    for (long long i = head[x]; i != 0; i = edge[i].next)
    {
        long long nex = edge[i].to;//此处nex下一个点,而i是下条边的编号,需要注意
        if (!vis[nex])
        {
            f[nex] = f[x] - zishu[nex] * edge[i].length + (zishu[1] - zishu[nex]) * edge[i].length;
            dp(x, nex);
        }
    }
    // cout << f[x] << " " << x << endl;
    minn = min(minn, f[x]);//每次求一次不方便值的最小值,最后输出
    return;
}
int main()
{

    // freopen("input.txt", "r", stdin);
    // freopen("output.txt", "w", stdout);
    long long n;
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", &qz[i]);
    }
    for (int i = 1; i < n; i++)
    {
        long long x, l, r;
        scanf("%lld%lld%lld", &l, &r, &x);
        add(r, l, x);//r<->l一条无向边
        add(l, r, x);
    }
    dfs(0, 1);
    memset(vis, 0, sizeof(vis));
    dp(1, 1);
    printf("%lld", minn);
    return 0;
}
posted @ 2021-10-21 21:57  多巴胺不耐受仿生人  阅读(84)  评论(0)    收藏  举报