题目

链接: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. Apache配置 1. 默认虚拟主机

    编辑httpd.conf搜索httpd-vhosts,去掉#号 # vi /usr/local/apache2.4/conf/httpd.conf Include conf/extra/httpd-v ...

  2. 02-Spring配置文件加载

    获取IOC容器 加载.解析xml文件,形成GenericBeanDefinition,供后续实例化剩下的所有 Bean 使用. obtainFreshBeanFactory() 获取IOC容器 pro ...

  3. MySQL入门(1)——基础操作

    MySQL入门(1)--基础操作 创建数据库 创建基本数据库: create database db_admin; 创建基本数据库(等价于CREATE DATABASE): create schema ...

  4. POJ_1227 Jack Straws 【二维平面判两线段相交】

    一 题面 POJ1127 二 分析 在平面几何中,判断两线段相交的方法一般是使用跨立实验.但是这题考虑了非严格相交,即如何两个线段刚好端点相交则也是相交的,所以还需要使用快速排斥实验. 这里参考并引用 ...

  5. c++ 反汇编 继承

    单继承,父类中没有虚函数 单继承,父类存在虚函数,子类重写虚函数 单继承,父类存在虚函数,子类不新定义虚函数 单继承,父类存在虚函数,子类新定义虚函数 单继承,父类不存在虚函数,子类定义虚函数 多继承 ...

  6. PTA 将数组中的数逆序存放

    7-1 将数组中的数逆序存放 (20 分)   本题要求编写程序,将给定的n个整数存入数组中,将数组中的这n个数逆序存放,再按顺序输出数组中的元素. 输入格式: 输入在第一行中给出一个正整数n(1). ...

  7. 攻防世界 reverse evil

    这是2017 ddctf的一道逆向题, 挑战:<恶意软件分析> 赛题背景: 员工小A收到了一封邮件,带一个文档附件,小A随手打开了附件.随后IT部门发现小A的电脑发出了异常网络访问请求,进 ...

  8. python3 输出字符

    import string'''whitespace -- a string containing all ASCII whitespaceascii_lowercase -- a string co ...

  9. 拖拽方式生成Vue用户界面

      前一阵子拜访了一些小伙伴,大家都表示苦前端太久了,需要花费不少时间在前端开发上.本着在不损失灵活性的前提下尽可能提高开发效率的原则,作者尝试在框架内集成了拖拽方式生成Vue用户界面的功能作为补充, ...

  10. JAVA题目:正整数n若是其平方数的尾部,则称n为同构数 如:5*5=25, 25*25=625 问: 求1~99中的所有同构数

    1 /*题目:正整数n若是其平方数的尾部,则称n为同构数 2 如:5*5=25, 25*25=625 3 问: 求1~99中的所有同构数 4 */ 5 //分析:将1-99分为1-9和10-99,用取 ...