【点分治】2019 首尔 icpc Gene Tree
题目
链接:https://ac.nowcoder.com/acm/contest/15644/B
来源:牛客网
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)
输入
4
1 4 1
4 3 1
2 4 1
输出
12
输入
6
1 5 1
5 2 1
5 6 1
6 4 3
6 3 2
输出
111
输入
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的更多相关文章
- UCloud首尔机房整体热迁移是这样炼成的
		
小结: 1.把两个机房在逻辑上变成一个机房: 2.新老机房的后端服务使用同一套 ZooKeeper,但是配置的却是不同的 IP: 3.UCloud内部服务所使用的数据库服务为MySQL, 内部MySQ ...
 - 区块链 - 默克尔树(Merkle Tree)
		
章节 区块链 – 介绍 区块链 – 发展历史 区块链 – 比特币 区块链 – 应用发展阶段 区块链 – 非对称加密 区块链 – 哈希(Hash) 区块链 – 挖矿 区块链 – 链接区块 区块链 – 工 ...
 - NGK Global首尔站:内存是未来获取数字财富的新模式
		
近日,NGK路演在NGK韩国社区的积极举办下顺利落下帷幕.此次路演在首尔举行,在活动当天,NGK的核心团队成员.行业专家.投资银行精英.生态产业代表和数百名NGK韩国社区粉丝一起参加NGK Globa ...
 - 点分治模板(洛谷P4178 Tree)(树分治,树的重心,容斥原理)
		
推荐YCB的总结 推荐你谷ysn等巨佬的详细题解 大致流程-- dfs求出当前树的重心 对当前树内经过重心的路径统计答案(一条路径由两条由重心到其它点的子路径合并而成) 容斥减去不合法情况(两条子路径 ...
 - 区块链入门到实战(12)之区块链 – 默克尔树(Merkle Tree)
		
目的:解决由于区块链过长,导致节点硬盘存不下的问题. 方法:只需保留交易的哈希值. 区块链作为分布式账本,原则上网络中的每个节点都应包含整个区块链中全部区块,随着区块链越来越长,节点的硬盘有可能放不下 ...
 - 2019.01.19 codeforces343D.Water Tree(树剖+ODT)
		
传送门 ODTODTODT板子题. 支持子树01覆盖,路径01覆盖,询问一个点的值. 思路:当然可以用树剖+线段树,不过树剖+ODTODTODT也可以很好的水过去. 注意修改路径时每次跳重链都要修改. ...
 - [多校联考2019(Round 5 T1)] [ATCoder3912]Xor Tree(状压dp)
		
[多校联考2019(Round 5)] [ATCoder3912]Xor Tree(状压dp) 题面 给出一棵n个点的树,每条边有边权v,每次操作选中两个点,将这两个点之间的路径上的边权全部异或某个值 ...
 - [Luogu P4178]Tree 题解(点分治+平衡树)
		
题目大意 给定一棵树,边带权,问有多少点对满足二者间距离$\leq K$,$n \leq 40000$. 题解 点分治专题首杀!$Jackpot!$ (本来看着题意比较简单想捡个软柿子捏,结果手断了… ...
 - 数值分析案例:Newton插值预测2019城市(Asian)温度、Crout求解城市等温性的因素系数
		
数值分析案例:Newton插值预测2019城市(Asian)温度.Crout求解城市等温性的因素系数 文章目录 数值分析案例:Newton插值预测2019城市(Asian)温度.Crout求解城市等温 ...
 
随机推荐
- 华为OD机试题
			
"""最长回文字符串问题"""# 说明:方法很多,这个是最简单,也是最容易理解的一个,利用了动态规化.# 先确定回文串的右边界i,然后以右边 ...
 - yolo训练数据集
			
最近了解了下yolov3的训练数据集部分,总结了以下操作步骤:(基于pytorch框架,请预先装好pytorch的相关组件) 1.下载ImageLabel软件对图片进行兴趣区域标记,每张图片对应一个x ...
 - 关于深度学习配置的一些tips
			
建立博客的第一天,将以前记录的一些东西存档下,方便查看. 1安装anaconda 2pycharm破解 配置环境变量3虚拟环境推荐是python3.5或3.6版本 4.安装numpy tensorfl ...
 - python常用数据处理库
			
Python之所以能够成为数据分析与挖掘领域的最佳语言,是有其独特的优势的.因为他有很多这个领域相关的库可以用,而且很好用,比如Numpy.SciPy.Matploglib.Pandas.Scikit ...
 - 扩展欧几里得算法(EXGCD)学习笔记
			
0.前言 相信大家对于欧几里得算法都已经很熟悉了.再学习数论的过程中,我们会用到扩展欧几里得算法(exgcd),大家一定也了解过.这是本蒟蒻在学习扩展欧几里得算法过程中的思考与探索过程. 1.Bézo ...
 - 解析库--XPath
			
from lxml import etree 2 text = ''' 3 <div> 4 <ul> 5 <li class = "item-0"&g ...
 - windows创建签名文件pfx
			
https://stackoverflow.com/questions/84847/how-do-i-create-a-self-signed-certificate-for-code-signing ...
 - DNS 缓存中毒--Kaminsky 攻击复现
			
0x00 搭建实验环境 使用3台Ubuntu 16.04虚拟机,可到下面的参考链接下载 攻击的服务是BIND9,由于条件限制,这里使用本地的一台虚拟机当作远程DNS解析器,关闭了DNSSEC服务,其中 ...
 - Java学习之随机数的用法
			
•前言 随机数的产生在一些代码中很常用,也是我们必须要掌握的. 而 Java 中产生随机数的方法主要有三种: new Random() Math.random() currentTimeMillis( ...
 - 文字变图片——GitHub 热点速览 v.21.14
			
作者:HelloGitHub-小鱼干 程序的力量,在 deep-daze 体现得淋漓尽致,你用一句话描述下你的图片需求,它就能帮你生成对应图片.同样的,appsmith 的力量在于你只要拖拽即可得到一 ...