@atcoder - CODE FESTIVAL 2017 Final - [email protected] Tree MST

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

给定 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

@[email protected]

完全图并不能直接套 prim 或者 kruskal。
并且也不像最小曼哈顿生成树一样,有比较好用的性质可以去除大量无用边。

考虑这类题的一个通用解法:boruvka(不会念)。
其实是基本无人问津的最小生成树算法(我也不知道为什么),但是可以将其算法思想拓展,解决一些完全图的最小生成树问题。
说是完全图,边权也有性质,而且往往会和点权挂钩(比如这道题)。

扯了这么多,所谓的 boruvka 算法是什么呢?
我们对于每一个连通块去找与它相邻的最小边。可以证明这样的边一定是存在于最小生成树之中(破圈法之类的都能证)。
于是我们把这些边加入最小生成树,并将连通块合并。
因为每次选最小边的时候,一条边最多会被选两次,所以最多执行 log 次寻找最小边的操作。

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

什么?你说这道题需要点分治来寻找路径最小值?然后复杂度就炸成 O(nlog^3n) 了?
NONONO。我们其实可以 O(n) 一次性给所有点找到它对应的最小值。

考虑所有连通块都是一个点的时候,我们可以做一个换根 dp 求出所有点的最小边(求距离一个点最近的点)。
由于是找最小值,重复经过一条边肯定不优,我们甚至不用去存 dp 的次小值,来避免从上往下传递 dp 值的时候走入同一棵子树。
这个过程是 O(n) 的。

那么连通块是一堆点的时候,我们怎么去排除与它同一连通块的点呢?
其实。。。很简单嘛。
假如最小边是同一连通块的,我们就去找与最小边不在同一连通块的次小边,不就解决了嘛。
dp 的时候存两维:最小边,与不和最小边在同一连通块的次小边。转移时讨论一下即可。
一次求最小边的复杂度为 O(n),所以总复杂度为 O(nlogn)。

@accepted [email protected]

#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);
}

@[email protected]

被数据结构困住的我,用点分治 + 优先队列写了一个 prim。
然后它 MLE 了。
当时我就哭了。

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

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11773724.html

时间: 2024-08-30 12:40:26

@atcoder - CODE FESTIVAL 2017 Final - [email protected] Tree MST的相关文章

[Atcoder Code Festival 2017 Qual B Problem F]Largest Smallest Cyclic Shift

题目大意:给你\(A\)个a,\(B\)个b,\(C\)个c,要你构造一个字符串,使它的最小循环表示法最大.求这个表示法.解题思路:不知道怎么证,但把a.b.c当做单独的字符串扔进容器,每次把字典序最小的和字典序最大的两个字符串合并就是答案.容器用multiset即可. C++ Code: #include<cstdio> #include<set> #include<string> using namespace std; multiset<string>

atcoder CODE FESTIVAL 2017 qual C D - Yet Another Palindrome Partitioning

Problem Statement We have a string s consisting of lowercase English letters. Snuke is partitioning s into some number of non-empty substrings. Let the subtrings obtained be s1, s2, …, sNfrom left to right. (Here, s=s1+s2+…+sN holds.) Snuke wants to

101 to 010 Atcoder CODE FESTIVAL 2017 qual B D

https://www.luogu.org/problemnew/show/AT3575 题解 根本不会.. 错误记录:缺少32行的转移.显然这个转移是必要的 1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<vector> 5 using namespace std; 6 #define fi first 7 #define se second 8 #define

[AtCoder Code Festival 2017 QualB C/At3574] 3 Steps - 二分图染色,结论

给你一个n个点m条边的无向图,进行以下操作 如果存在两个点u和v,使得从u走三步能恰好到达v,那么在u和v之间连接一条边 重复这个操作直到不能再连接新的边,问最后有多少条边? n, m <= 100000 如果一个连通块包含奇环它一定会变成完全图 如果一个连通块没有奇环,那么它一定是二分图,二分图一定会变成"完全二分图" #include <bits/stdc++.h> using namespace std; #define int long long const

@COCI 2016/2017 Round [email&#160;protected] Meksikanac

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 在平面直角坐标系中,给定一个左下角为 (0, 0),右上角为 (Xp, Yp) 的矩形,并给定矩形内的 N 个点. 已知坐标系内有一个 K 边形,现将这个 K 边形平移. 问有多少种方案使最后 K 边形落入给定的矩形,定点在整数点上,且不包含 N 个点中的任意一个(与 K 边形的边.顶

【Atcoder】CODE FESTIVAL 2017 qual C D - Yet Another Palindrome Partitioning

[题意] 给定只含小写字母的字符串,要求分割成若干段使段内字母重组顺序后能得到回文串,求最少分割段数.n<=2*10^5 [题解] 关键在于快速判断一个字符子串是否合法,容易发现合法仅当不存在或只存在一个奇数字符,其余字符均为偶数. 当涉及到奇偶性(%2)时,很自然能想到异或. 将小写字母a~z转化2^0~2^25,那么一个字符子串合法当且仅当其连续异或值是0或2^i(0<=i<=25). 令f[i]表示前i个合法的最少段数,sum[i]表示异或前缀和,则有: f[i]=min(f[j]

【AtCoder】CODE FESTIVAL 2017 qual C

A - Can you get AC? No #include <bits/stdc++.h> #define fi first #define se second #define pii pair<int,int> #define pb push_back #define mp make_pair using namespace std; typedef long long int64; char s[15]; int main() { scanf("%s",

【AtCoder】CODE FESTIVAL 2017 qual B

最近不知道为啥被安利了饥荒,但是不能再玩物丧志了,不能颓了 饥荒真好玩 A - XXFESTIVAL CCFESTIVAL #include <bits/stdc++.h> #define fi first #define se second #define pii pair<int,int> #define mp make_pair #define pb push_back #define space putchar(' ') #define enter putchar('\n'

CODE FESTIVAL 2017 qual A C Palindromic Matrix(思维题)

题目链接:点我呀 题意:给出n*m由26位小写字母组成的矩阵,问是否能够重构形成一个每行每列都是回文的矩阵 题解:分三种情况考虑(偶偶,奇奇,奇偶),每种情况下考虑最少 需要4个相同字母的字母数,2个相同字母的字母数和一个字母的字母数(只有奇奇的时候才需要一个字母) 最后判断一下.感觉自己的这个代码不是很简洁,明天起来看看别人怎么写的,睡觉去... 1 //Atcoder 3 2 #include <cstring> 3 #include <iostream> 4 #include