树的重心
树的重心
所谓重心是在树中删除后使得剩下的连通块节点数最大的最小的点
重心的性质:
- 树上所有点到一点的距离(可带权)之和是到所有点距离之和中最短的
- 连接两颗树其新重心在于两树的重心的路径上
- 树增加或删除一个叶子,重心最多移动一个节点
- 树的重心若不唯一最多为2,且两个重心相邻
- 以重心为根的子树的大小不会超过总节点数的一半
求重心
#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;
}
本文来自博客园,作者:多巴胺不耐受仿生人,转载请注明原文链接:https://www.cnblogs.com/VoidCoderTF/articles/15435416.html

浙公网安备 33010602011771号