题目

链接:https://ac.nowcoder.com/acm/contest/15644/B
来源:牛客网

A gene tree is a tree showing the evolution of various genes or biological species. A gene tree represents the relatedness of specific genes stored at the leaf nodes without assumption about their ancestry. Leaf nodes represent genes, called taxa, and internal nodes represent putative ancestral taxa. Each edge in the tree is associated with a positive integer, phylogenetic length, which quantifies the evolutionary distance between two nodes of the edge. For example, the left figure below shows a gene tree with six leaf nodes, which approximates the relation among six taxa, and the right one shows a gene tree with four taxa.


Like the trees ܶT1T_1T1​ and ܶT2T_2T2​ above, gene trees are modeled as unrooted trees where each internal node (non-leaf node) has degree three. A path-length between two leaf nodes is the sum of the phylogenetic lengths of the edges along the unique path between them. In ܶT1T_1T1​, the path-length between Human and Cow is 2 + 3 = 5 and the path-length between Human and Goldfish is 2 + 4 + 8 + 10 = 24. These lengths indicate that Human is much closer to Cow than to Goldfish genetically. From ܶT2T_2T2​, we can guess that the primate closest to Human is Chimpanzee.

Researchers are interested in measuring the distance between genes in the tree. A famous distance measure is the sum of squared path-lengths of all unordered leaf pairs. More precisely, such a distance ݀d(ܶT) is defined as follows:
d(T)=∑unordered pair(u,v)pu,v2d(T)=\sum_{unordered\,pair(u,v)}p^2_{u,v}d(T)=∑unorderedpair(u,v)​pu,v2​
where pu,vp_{u,v}pu,v​ is a path-length between two leaf nodes u and v in ܶT. Note that ݀d(ܶT) is the sum of the squared path-lengths pu,v2p^2_{u,v}pu,v2​ over all unordered leaf pairs u and v in ܶT. For the gene tree ܶT2T_2T2​ in Figure B.1, there are six paths over all unordered leaf pairs, (Human, Chimpanzee), (Human, Gorilla), (Human, Orangutan), (Chimpanzee, Gorilla), (Chimpanzee, Orangutan), and (Gorilla, Orangutan). The sum of squared path-lengths is 22+42+52+42+52+52=1112^2 + 4^2 + 5^2 + 4^2 + 5^2 + 5^2 = 11122+42+52+42+52+52=111, so ݀d(ܶT2)d(ܶT_2)d(ܶT2​) = 111.

Given an unrooted gene tree T, write a program to output ݀d(T).
 

输入描述:

Your program is to read from standard input. The input starts with a line containing an integer n (4 ≤ n ≤ 100,000), where n is the number of nodes of the input gene tree ܶT. Then ܶT has n − 1 edges. The nodes of ܶT are numbered from 1 to n. The following n − 1 lines represent n − 1 edges of ܶT, where each line contains three non-negative integers ܽa,b, and ݈l (1 ≤ ܽa ≠ ܾb ≤ n, 1 ≤ ݈l ≤ 50) where two nodes ܽa and ܾb form an edge with phylogenetic length ݈l.

输出描述:

Your program is to write to standard output. Print exactly one line. The line should contain one positive  integer d(ܶT)

示例1

输入

4
1 4 1
4 3 1
2 4 1

输出

12

示例2

输入

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

输出

111

示例3

输入

10
1 2 10
10 2 7
3 2 8
3 9 3
9 8 2
7 9 1
6 4 3
4 5 2
3 4 4

 

输出

4709

题意

给你一个无根树,求任意两叶节点路径和的平方和。

 

题解

正解好像是换根dp,但我因为比赛时昨天看了半小时点分治,一直以为是点分治,当时比赛时点分治学的不行,最后改完bug交完后tle,补题时才知道,点分治是每一个子树都找一次重心,才能达到nlogn的复杂度。

我不是dp选手所以不懂换根dp怎么搞,就讲讲点分治吧。

点分治,实际上是树上分治算法,它可以很好的处理树上路径问题。它把一颗树看成根节点与他的子树,同时它每一个子树也可以分成一个根节点和子树。以这个为分治的单位。

树上的所有路径按这种分法,实际上就两种情况:

1.路径经过根节点。

2.路径不经过根节点。

就考虑这两种情况,然后我们一步步分治下去,就可以找到所有答案。

第二种情况由分治来解决,我们就只要处理第一种情况。

两叶节点的路径长度可以表示为两个叶节点到根节点距离的和,所以我们只需要求。数组dis[x]表示节点x到根节点的距离,dfs一遍就可以求出所有的dis,这样我们利用dis就可以在O(1)的复杂度中求出任意两叶节点的长度。当然只有这个还是不够,这样两两匹配复杂度是O(n^2)是数据不能容忍的复杂度。但是我们很容易想到,我们能用组合数学的方法成组的找到答案,如有3个叶节点,a1,a2,a3,任意两叶节点路径和的平方和是,a1-a2,a1-a3,a2-a3,这3条路径的平方和,即(dis[a1]+dis[a2])^2+(dis[a1]+dis[a3])^2+(dis[a2]+dis[a3])^2,显然,化简该公式得到,

设dis[ai]=di

2*d1^2+2*d1*(d2+d3)+d2^2+d3^2 +(d2+d3)^2

我们发现先不考虑a2-a3的情况,就从a1出发到其他节点的值为

设n为叶节点个数,sum(i,j)为di到dj的和,ssum(i,j)为di到dj的平方和

(n-1)*d1^2+2*d1*sum(2,n)+ssum(2,n)

其他的路径,如a2-a3,也可以表示为去掉a1剩下的从a2开始的节点的路径的平方和

所以这个公式就可以推广为

(n-1)*d1^2+2*d1*sum(2,n)+ssum(2,n)+(n-2)*d2^2+2*d2*sum(3,n)+ssum(3,n)+...

然后sum和ssum可以使用前缀和维护,这样我们就可以在O(n)的复杂度中求出任意两点的平方和

上面我们讨论的都是子树只有单个叶节点的情况,如果子树有多个叶节点,那我们就会把同子树的叶节点也算上,但同子树的叶节点路径不通过根节点,所以我们需要改动下,最简单的方法就是单个单个计数,计数时不考虑同子树的,也容易实现,只要dfs求出bt[X],表是节点X在根节点的哪个子树,然后使用bt[X]来划分叶节点就可行,通过一些预处理,也能达到O(n)的复杂度。

但实际上有种更优的方法,

很容易发现同子树的连接的节点都是相同的,我们可以从这点优化,

设a1,a2,a3为同子树的叶节点,m为除去这3节点的剩下节点的个数,sum为剩下节点的和,ssum为剩下节点的平方和,则有

m*d1^2+2*d1*sum+ssum+m*d2^2+2*d2*sum+ssum+m*d3^2+2*d3*sum+ssum'

变形得

m*(d1^2+d2^2+d3^2)+2*(d1+d2+d3)*sum+3*ssum

推广得

设n为同子树叶节点个数,sum1为同子树叶节点和,ssum1为平方和,sum2为剩下节点和,ssum2为平方和

m*ssum1+2*sum1*sum2+n*sum2

这样就可以成块的处理节点,并且使用前缀和可以非常方便快速的维护

由于点分治每一次递归都会重新寻找一次重心,所以每一次分治都会减少一半的大小,所以最终的复杂度是O(nlogn)

代码

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<queue>
#include<cstring>
#include<ctime>
#include<string>
#include<vector>
#include<map>
#include<list>
#include<set>
#include<stack>
#include<bitset>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll> pii;
typedef pair<ll, ll> pll;
const ll N = 1e5 + 5;
const ll mod = 1e9 + 7;
const double gold = (1 + sqrt(5)) / 2.0;
const double PI = acos(-1);
const double eps = 1e-7;
const ll dx[] = { 0,1,0,-1 };
const ll dy[] = { 1,0,-1,0 };
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a%b); }
ll pow(ll x, ll y, ll mod) { ll ans = 1; while (y) { if (y & 1)ans = (ans* x) % mod; x = (x*x) % mod; y >>= 1; }return ans; }
ll pow(ll x, ll y) { ll ans = 1; while (y) { if (y & 1)ans = (ans* x) % mod; x = (x*x) % mod; y >>= 1; }return ans; } struct node {
ll to, w;
node() {}
node(ll a, ll b) :to(a), w(b) {}
};
vector<node> e[N]; ll Gsize[N];
ll n;
ll Gans, root;
ll vis[N]; //长链缩边
ll from, Pid;
ll CDSPsum;
ll CDSPcnt;
ll CDSPnum;
void cdsp(ll x, ll f) { if (e[x].size() == 2) {
CDSPcnt++;
vis[x] = 1;
if (e[x][0].to != f) {
CDSPsum += e[x][0].w;
cdsp(e[x][0].to, x);
}
else {
CDSPsum += e[x][1].w;
cdsp(e[x][1].to, x);
}
}
else {
ll a = from, b = Pid, d = CDSPcnt;
ll c = CDSPsum;
for (ll i = 0; i < e[x].size(); i++) {
ll y = e[x][i].to;
if (y == f) {
if (CDSPsum&&from&&from != f) {
e[a][b].to = x;
e[a][b].w = c;
e[x][i].to = a;
e[x][i].w = c;
CDSPnum -= d;
}
continue;
}
from = x;
Pid = i;
CDSPsum = e[x][i].w;
CDSPcnt = 0;
cdsp(y, x);
}
} } //计数
ll tnum;
void getnum(ll x) {
tnum++; for (int i = 0; i < e[x].size(); i++) {
ll y = e[x][i].to;
if (vis[y])continue;
vis[y] = 1;
getnum(y);
vis[y] = 0;
} } //找重心 void Gdfs(ll x) { Gsize[x] = 1;
ll mp = 0;
for (ll i = 0; i < e[x].size(); i++) {
ll y = e[x][i].to;
if (vis[y])continue;
vis[y] = 1;
Gdfs(y);
Gsize[x] += Gsize[y];
if (mp < Gsize[y])
mp = Gsize[y];
vis[y] = 0; }
mp = max(mp, tnum - Gsize[x]);
if (mp < Gans) {
Gans = mp;
root = x;
} } ll dis[N];
ll bt[N];
ll leaf[N];
ll llen;
void dfs(ll x) { if (e[x].size() == 1 && x != root) {
leaf[++llen] = x;
}
for (ll i = 0; i < e[x].size(); i++) {
ll y = e[x][i].to;
if (vis[y])continue;
if (x != root)bt[y] = bt[x];
vis[y] = 1;
dis[y] = dis[x] + e[x][i].w;
dfs(y);
vis[y] = 0;
}
} ll ans;
ll sf[N], ssf[N]; //点分治
ll L[N], R[N];
ll slen;
void calc(ll x) { Gans = 1e9;
tnum = 0;
vis[x] = 1;
getnum(x);
Gdfs(x);
vis[x] = 0;
x = root;
bt[x] = x;
for (ll i = 0; i < e[x].size(); i++) {
bt[e[x][i].to] = e[x][i].to;
}
llen = 0;
//for(ll i=0;i<=n;i++){
// dis[i]=0;
//}
dis[x] = 0;
vis[x] = 1;
dfs(x);
vis[x] = 0;
sf[0] = ssf[0] = 0;
for (ll i = 1; i <= llen; i++) {
sf[i] = sf[i - 1] + dis[leaf[i]];
ssf[i] = ssf[i - 1] + dis[leaf[i]] * dis[leaf[i]];
} ll l = 1, r = 1, tip = 0;
slen = 0;
for (; r <= llen; r++) {
if (tip == 0) {
tip = bt[leaf[r]];
}
if (tip != bt[leaf[r + 1]]) {
L[slen] = l;
R[slen++] = r;
tip = 0;
l = r + 1;
} } if (tip) {
L[slen] = l;
R[slen++] = r-1;
}
for (ll i = 0; i < slen - 1; i++) {
ll suma = sf[R[i]] - sf[L[i] - 1];
ll ssuma = ssf[R[i]] - ssf[L[i] - 1];
ll sumb = sf[R[slen - 1]] - sf[L[i + 1] - 1];
ll ssumb = ssf[R[slen - 1]] - ssf[L[i + 1] - 1];
ans += (R[slen - 1] - L[i + 1] + 1)*ssuma + (R[i] - L[i] + 1)*ssumb + 2 * suma*sumb;
} vis[root] = 1;
for (ll i = 0; i < e[x].size(); i++) {
ll y = e[x][i].to;
if (vis[y])continue;
calc(y);
}
} inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch<'0' || ch>'9') { if (ch == '-')w = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
} int main() { scanf("%lld", &n);
ll a, b, v;
ll lf;
for (ll i = 0; i < n - 1; i++) {
a = read();
b = read();
v = read();
e[a].emplace_back(node(b, v));
e[b].emplace_back(node(a, v));
} for (ll i = 1; i <= n; i++) {
if (e[i].size() == 1) {
lf = i;
break;
}
}
//这缩边实际上速度影响不大,快了3ms。 CDSPnum = n;
cdsp(lf, 0);
if (CDSPnum == 2) {
ans = e[lf][0].w*e[lf][0].w;
} calc(lf); printf("%lld\n", ans); scanf(" ");
return 0;
}

【点分治】2019 首尔 icpc Gene Tree的更多相关文章

  1. UCloud首尔机房整体热迁移是这样炼成的

    小结: 1.把两个机房在逻辑上变成一个机房: 2.新老机房的后端服务使用同一套 ZooKeeper,但是配置的却是不同的 IP: 3.UCloud内部服务所使用的数据库服务为MySQL, 内部MySQ ...

  2. 区块链 - 默克尔树(Merkle Tree)

    章节 区块链 – 介绍 区块链 – 发展历史 区块链 – 比特币 区块链 – 应用发展阶段 区块链 – 非对称加密 区块链 – 哈希(Hash) 区块链 – 挖矿 区块链 – 链接区块 区块链 – 工 ...

  3. NGK Global首尔站:内存是未来获取数字财富的新模式

    近日,NGK路演在NGK韩国社区的积极举办下顺利落下帷幕.此次路演在首尔举行,在活动当天,NGK的核心团队成员.行业专家.投资银行精英.生态产业代表和数百名NGK韩国社区粉丝一起参加NGK Globa ...

  4. 点分治模板(洛谷P4178 Tree)(树分治,树的重心,容斥原理)

    推荐YCB的总结 推荐你谷ysn等巨佬的详细题解 大致流程-- dfs求出当前树的重心 对当前树内经过重心的路径统计答案(一条路径由两条由重心到其它点的子路径合并而成) 容斥减去不合法情况(两条子路径 ...

  5. 区块链入门到实战(12)之区块链 – 默克尔树(Merkle Tree)

    目的:解决由于区块链过长,导致节点硬盘存不下的问题. 方法:只需保留交易的哈希值. 区块链作为分布式账本,原则上网络中的每个节点都应包含整个区块链中全部区块,随着区块链越来越长,节点的硬盘有可能放不下 ...

  6. 2019.01.19 codeforces343D.Water Tree(树剖+ODT)

    传送门 ODTODTODT板子题. 支持子树01覆盖,路径01覆盖,询问一个点的值. 思路:当然可以用树剖+线段树,不过树剖+ODTODTODT也可以很好的水过去. 注意修改路径时每次跳重链都要修改. ...

  7. [多校联考2019(Round 5 T1)] [ATCoder3912]Xor Tree(状压dp)

    [多校联考2019(Round 5)] [ATCoder3912]Xor Tree(状压dp) 题面 给出一棵n个点的树,每条边有边权v,每次操作选中两个点,将这两个点之间的路径上的边权全部异或某个值 ...

  8. [Luogu P4178]Tree 题解(点分治+平衡树)

    题目大意 给定一棵树,边带权,问有多少点对满足二者间距离$\leq K$,$n \leq 40000$. 题解 点分治专题首杀!$Jackpot!$ (本来看着题意比较简单想捡个软柿子捏,结果手断了… ...

  9. 数值分析案例:Newton插值预测2019城市(Asian)温度、Crout求解城市等温性的因素系数

    数值分析案例:Newton插值预测2019城市(Asian)温度.Crout求解城市等温性的因素系数 文章目录 数值分析案例:Newton插值预测2019城市(Asian)温度.Crout求解城市等温 ...

随机推荐

  1. golang——gRPC学习

    1.获取gRPC 环境变量GOPATH的src目录下执行: git clone https://github.com/grpc/grpc-go.git google.golang.org/grpc g ...

  2. FreeBSD WIFI 配置详细介绍

    首先运行ifconfig,看看能不能找到你的网卡,如果能,那么你可以走了﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉﹉运行sysctl net.wlan.devices,他可以告诉你,找到的无线网卡编辑/b ...

  3. Flutter教程- Dart语言规范-知识点整理

    Flutter教程- Dart语言知识点整理 Dart语言简介 Dart语言介绍 ① 注释的方式 ② 变量的声明 ③ 字符串的声明和使用 ④ 集合变量的声明 ⑤ 数字的处理 ⑥ 循环的格式 ⑦ 抛异常 ...

  4. vue中常见的问题以及解决方法

    有一些问题不限于 Vue,还适应于其他类型的 SPA 项目. 1. 页面权限控制和登陆验证 页面权限控制 页面权限控制是什么意思呢? 就是一个网站有不同的角色,比如管理员和普通用户,要求不同的角色能访 ...

  5. java例题_40 字母字符串转数组后排序

    1 /*40 [程序 40 字符串排序] 输入一个字符串数组,按照字母表的降序对这些字符串进行排序. 2 题目:字符串排序. 3 */ 4 5 /*分析 6 * 1.从键盘得到一个纯字母的字符串 7 ...

  6. DAOS 分布式异步对象存储|存储模型

    概述 DAOS Pool 是分布在 Target 集合上的存储资源预留.分配给每个 Target 上的 Pool 的实际空间称为 Pool Shard. 分配给 Pool 的总空间在创建时确定,后期可 ...

  7. 【linux】驱动-8-一文解决设备树

    目录 前言 8. Linux设备树 8.1 设备树简介 8.2 设备树框架 8.2.1 设备树格式 8.2.1.1 DTS 文件布局 8.2.1.2 node 格式 8.2.1.3 propertie ...

  8. PaddleOCR详解

    @ 目录 PaddleOCR简介 环境配置 PaddleOCR2.0的配置环境 Docker 数据集 文本检测 使用自己的数据集 文本识别 使用自己的数据集 字典 自定义字典 添加空格类别 文本角度分 ...

  9. JS实现环绕地球飞行的3D飞行线动画效果(JS+HTML)

    1.项目介绍 JS+HTML实现绕地球飞行的3D飞行线动画效果,且3D地球可以随意拖动和滑动缩放,画面中心是蓝色地球,地球表面上的两点连线之间有光电随机出现沿着抛物线轨迹3D飞行,可使用较好的浏览器打 ...

  10. CMD控制台(命令提示符)的打开方式

    打开CMD的方式 打开+系统+命令提示符 Win键 +R 输入cmd 打开控制台(推荐使用) 在任意的文件夹下面,按住shift键+鼠标右键点击+在此处打开命令行窗口 资源管理器的地址栏前面加上cmd ...