@description@

给定 N 个点,第 i 点有一个点权 Xi,再给定一棵边带权的树,第 i 条 (Ai, Bi) 边权为 Ci。

构建一个完全图,完全图中边 (i, j) 的边权为 dist(i, j) + Xi + Xj,其中 dist(i, j) 是点 i 与点 j 在树上的距离。

求该完全图的最小生成树。

Constraints

2≤N≤200000; 1≤Xi≤10^9; 1≤Ai,Bi≤N; 1≤Ci≤10^9

Input

输入形式如下:

N

X1 X2 … XN

A1 B1 C1

A2 B2 C2

:

AN−1 BN−1 CN−1

Output

输出最小生成树的边权总和。

Sample Input 1

4

1 3 5 1

1 2 1

2 3 2

3 4 3

Sample Output 1

22

选择边 (1, 2), (1, 4), (3, 4),边权总和为 5 + 8 + 9 = 22

点此跳转到题目

@solution@

完全图并不能直接套 prim 或者 kruskal。

并且也不像最小曼哈顿生成树一样,有比较好用的性质可以去除大量无用边。

考虑这类题的一个通用解法:boruvka(不会念)。

其实是基本无人问津的最小生成树算法(我也不知道为什么),但是可以将其算法思想拓展,解决一些完全图的最小生成树问题。

说是完全图,边权也有性质,而且往往会和点权挂钩(比如这道题)。

扯了这么多,所谓的 boruvka 算法是什么呢?

我们对于每一个连通块去找与它相邻的最小边。可以证明这样的边一定是存在于最小生成树之中(破圈法之类的都能证)。

于是我们把这些边加入最小生成树,并将连通块合并。

因为每次选最小边的时候,一条边最多会被选两次,所以最多执行 log 次寻找最小边的操作。

而这类题的特点是:往往寻找最小边的过程可以优化。

什么?你说这道题需要点分治来寻找路径最小值?然后复杂度就炸成 O(nlog^3n) 了?

NONONO。我们其实可以 O(n) 一次性给所有点找到它对应的最小值。

考虑所有连通块都是一个点的时候,我们可以做一个换根 dp 求出所有点的最小边(求距离一个点最近的点)。

由于是找最小值,重复经过一条边肯定不优,我们甚至不用去存 dp 的次小值,来避免从上往下传递 dp 值的时候走入同一棵子树。

这个过程是 O(n) 的。

那么连通块是一堆点的时候,我们怎么去排除与它同一连通块的点呢?

其实。。。很简单嘛。

假如最小边是同一连通块的,我们就去找与最小边不在同一连通块的次小边,不就解决了嘛。

dp 的时候存两维:最小边,与不和最小边在同一连通块的次小边。转移时讨论一下即可。

一次求最小边的复杂度为 O(n),所以总复杂度为 O(nlogn)。

@accepted code@

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
#define mp make_pair
#define fi first
#define se second
typedef pair<ll, int> pli;
typedef pair<pli, pli> st;
const int MAXN = 200000;
const ll INF = (1LL<<60);
struct edge{
int to; ll dis;
edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = edges;
void addedge(int u, int v, int w) {
edge *p = (++ecnt);
p->to = v, p->dis = w, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->dis = w, p->nxt = adj[v], adj[v] = p;
}
pli lnk[MAXN + 5];
int fa[MAXN + 5], clr[MAXN + 5];
ll X[MAXN + 5];
int find(int x) {
return fa[x] = (fa[x] == x ? x : find(fa[x]));
}
bool unite(int x, int y) {
int fx = find(x), fy = find(y);
if( fx != fy ) {
fa[fx] = fy;
return true;
}
else return false;
}
st dp[MAXN + 5];
void update(st &a, st b) {
if( b.fi.fi < a.fi.fi ) {
if( b.fi.se != a.fi.se )
a.se = a.fi;
a.fi = b.fi;
if( b.se.fi < a.se.fi )
a.se = b.se;
}
else {
if( b.fi.se != a.fi.se ) {
if( b.fi.fi < a.se.fi )
a.se = b.fi;
}
else if( b.se.fi < a.se.fi )
a.se = b.se;
}
}
void dfs1(int x, int f) {
dp[x] = mp(mp(X[x], clr[x]), mp(INF, -1));
for(edge *p=adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
dfs1(p->to, x); st t = dp[p->to];
t.fi.fi += p->dis, t.se.fi += p->dis;
update(dp[x], t);
}
}
void dfs2(int x, int f, st k) {
update(dp[x], k);
for(edge *p=adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
st t = dp[x];
t.fi.fi += p->dis, t.se.fi += p->dis;
dfs2(p->to, x, t);
}
}
int num[MAXN + 5];
int main() {
int N; scanf("%d", &N);
for(int i=1;i<=N;i++)
scanf("%lld", &X[i]), fa[i] = i;
for(int i=1;i<N;i++) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
}
ll ans = 0;
while( true ) {
int cnt = 0;
for(int i=1;i<=N;i++)
if( fa[i] == i ) num[clr[i] = (++cnt)] = i;
if( cnt == 1 ) break;
for(int i=1;i<=N;i++)
clr[i] = clr[find(i)];
dfs1(1, 0), dfs2(1, 0, mp(mp(INF, -1), mp(INF, -1)));
for(int i=1;i<=cnt;i++)
lnk[i] = mp(INF, -1);
for(int i=1;i<=N;i++) {
if( dp[i].fi.se == clr[i] )
lnk[clr[i]] = min(lnk[clr[i]], mp(dp[i].se.fi + X[i], dp[i].se.se));
else lnk[clr[i]] = min(lnk[clr[i]], mp(dp[i].fi.fi + X[i], dp[i].fi.se));
}
for(int i=1;i<=cnt;i++)
if( unite(num[i], num[lnk[i].se]) )
ans += lnk[i].fi;
}
printf("%lld\n", ans);
}

@details@

被数据结构困住的我,用点分治 + 优先队列写了一个 prim。

然后它 MLE 了。

当时我就哭了。

咳咳。不过也算是增长了一点见识,同时还告诉我一个深刻的道理:不要被套路所困。不一定非得要数据结构才能维护的啊。

@atcoder - CODE FESTIVAL 2017 Final - J@ Tree MST的更多相关文章

  1. Atcoder CODE FESTIVAL 2016 Final G - Zigzag MST[最小生成树]

    题意:$n$个点,$q$次建边,每次建边选定$x,y$,权值$c$,然后接着$(y,x+1,c+1),(x+1,y+1,c+2),(y+1,x+2,c+3),(x+2,y+2,c+4)\dots$(画 ...

  2. 【赛时总结】 ◇赛时·IV◇ CODE FESTIVAL 2017 Final

    ◇赛时-IV◇ CODE FESTIVAL 2017 Final □唠叨□ ①--浓浓的 Festival 气氛 ②看到这个比赛比较特别,我就看了一看--看到粉粉的界面突然开心,所以就做了一下 `(* ...

  3. [AtCoder Code Festival 2017 QualB D/At3575] 101 to 010 - dp

    [Atcoder Code Festival 2017 QualB/At3575] 101 to 010 有一个01序列,每次可以选出一个101,使其变成010,问最优策略下能操作几次? 考虑像 11 ...

  4. 【AtCoder】CODE FESTIVAL 2017 Final

    A - AKIBA 模拟即可 代码 #include <bits/stdc++.h> #define fi first #define se second #define pii pair ...

  5. AtCoder Code Festival 2017 Team Relay J - Indifferent

    题目大意:共$2n$个价格$p_i$.两人轮流取.你每次取最大的,对方每次随机取.问你取的期望和是多少. 题解:从小到大排序,$\sum\limits_{i=0}^{2n-1} \frac{i*p_i ...

  6. 题解【AtCoder - CODE FESTIVAL 2017 qual B - D - 101 to 010】

    题目:https://atcoder.jp/contests/code-festival-2017-qualb/tasks/code_festival_2017_qualb_d 题意:给一个 01 串 ...

  7. 【题解】Popping Balls AtCoder Code Festival 2017 qual B E 组合计数

    蒟蒻__stdcall终于更新博客辣~ 一下午+一晚上=一道计数题QAQ 为什么计数题都这么玄学啊QAQ Prelude 题目链接:这里是传送门= ̄ω ̄= 下面我将分几个步骤讲一下这个题的做法,大家不 ...

  8. Atcoder CODE FESTIVAL 2017 qual C D - Yet Another Palindrome Partitioning 回文串划分

    题目链接 题意 给定一个字符串(长度\(\leq 2e5\)),将其划分成尽量少的段,使得每段内重新排列后可以成为一个回文串. 题解 分析 每段内重新排列后是一个回文串\(\rightarrow\)该 ...

  9. Atcoder CODE FESTIVAL 2017 qual C C - Inserting 'x' 回文串

    题目链接 题意 给定字符串\(s\),可以在其中任意位置插入字符\(x\). 问能否得到一个回文串,若能,需插入多少个\(x\). 思路 首先统计出现次数为奇数的字符\(cnt\). \(cnt\ge ...

随机推荐

  1. ubuntu中vi下删除键和上下左右键输入字符异常(ABCD)

    刚安装的Ubuntu系统,使用vi编辑文本的时候, 出现以下现象: 点删除键输入了 D 回车无效 上下左右为字母 光标乱跳 原因: 自带的vi功能问题 解决: 卸载原有vi,重新安装完整版本vim 执 ...

  2. JS 过滤HTML标签,取得纯文本

    一.过滤掉所有HTML标签如下: str.innerHTML.replace(/<.*?>/g,"") 二.过滤掉带属性的某一个标签,如<span class=' ...

  3. day37 08-Hibernate的反向工程

    反向工程:先创建表,创建好表之后,就是持久化类和映射文件可以不用你写,而且你的DAO它也可以帮你生成.但是它生成的DAO可能会多很多的方法.你可以不用那么多方法,但是它里面提供了这种的.用hibern ...

  4. 【洛谷P1827】【USACO】 美国血统 American Heritage 由二叉树两个序列求第三个序列

    P1827 美国血统 American Heritage 题目描述 农夫约翰非常认真地对待他的奶牛们的血统.然而他不是一个真正优秀的记帐员.他把他的奶牛 们的家谱作成二叉树,并且把二叉树以更线性的&q ...

  5. php pdo操作数据库的方法

    PDO 安装 你可以通过 PHP 的 phpinfo() 函数来查看是否安装了PDO扩展. 1.在 Unix /linux系统上安装 PDO 在Unix上或Linux上你需要添加以下扩展: exten ...

  6. hdu 4722 Good Numbers( 数位dp入门)

    Good Numbers Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Tota ...

  7. 官网svn提交到代码库,但是不能同步到web目录

    现象:数据库名字存在,但是数据库中表为空:svn提交代码能到svn库中,但不能同步到web目录. 原因:磁盘分区满了,导致内测服务器中数据库先删掉但没能从线上拉取回来,显示数据库中表为空: 磁盘满,导 ...

  8. gb2312的6763个汉字

    0a啊阿埃挨哎唉哀皑癌蔼矮艾碍爱隘鞍氨安俺按暗岸胺案肮昂盎凹敖熬翱袄傲奥懊澳芭捌扒叭吧笆八疤巴拔跋靶把耙坝霸罢爸白柏百摆佰败拜稗斑班搬扳般颁板版扮拌伴瓣半办绊邦帮梆榜膀绑棒磅蚌镑傍谤苞胞包褒剥薄雹保堡 ...

  9. JAVA的一次编译,到处执行,你知道多少?

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/wangyongxia921/article/details/28117155   一.对AVA的迷茫 ...

  10. iOS 检测耳机插入/拔出

    http://www.verydemo.com/demo_c134_i28481.html 开发过程中录音和播放这块碰到了一些问题,麻烦的主要有三个: 检测是否有声音输入设备 当有多个声音输出设备时, ...