CJOJ 2403 次小生成树

CJOJ 2403 次小生成树

Description

小 C 最近学了很多最小生成树的算法,Prim 算法、Kurskal 算法、消圈算法等等。 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说: 如果最小生成树选择的边集是 EM,严格次小生成树选择的边集是 ES,那么需要满足:(value(e) 表示边 e的权值)

这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

Input

第一行包含两个整数N 和M,表示无向图的点数与边数。
接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z。

Output

包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

Sample Input

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

Sample Output

11

Hint

数据范围:
数据中无向图无自环;
50% 的数据N≤2 000 M≤3 000;
80% 的数据N≤50 000 M≤100 000;
100% 的数据N≤100 000 M≤300 000 ;
边权值非负且不超过 10^9 。

Source

BZOJ 1977,次小生成树

Solution

先求出最小生成树的大小,然后枚举没有选入的点如果选入后与最小生成树的差,找到最小的差之后加入到最小生成树中去,就是次小生成树

在找最小生成树的过程中,将每一个最小生成树的点加如数组中,方便以后用他们更新次小生成树需要替换的点(因为次小生成数是在最小生成树上进行修改的,所以只需要搜索最小生成树中点可以遍历到的点)

进行倍增搜索,更新每一次跳到的点,以及跳的过程中最大距离(d1)和次大距离(d2)-->只用在最大生成树上修改,所以只用记录这两个距离,递归调用即可

对于每一条没有加入最小生成树中边的两个端点求他们在最小生成树上的最近公共祖先(因为这条边没有加入最小生成树,所以在找LCA时不会通过这个边,换句话说在这里找到的LCA不是两端点的任意一个),然后用他们的该LCA更新如果加入这条边与最小生成树中的那条与该边某一端点相连的边的差的最小值(可能有点绕,也不知道些明白没,不明白就看代码吧cal函数),然后在所有没有加入的边的最小值中,找一个最小值加入最小生成树的大小中,即为次小生成树

Code

#include 
#include 
#include 
#include 
#include 
#include 
#define N 100001
#define M 300001
#define LL long long
using namespace std;

struct node {int x, y, d; bool bj;} e[M];
struct edge {int to, nxt, v;} a[N * 2];
int n, m, fa[N], cnt, head[N], deep[N], d1[N][17], d2[N][17], f[N][17], temp = 0x7fffffff, tot;
LL ans;

inline bool comp(node a, node b) {
  return a.d < b.d;
}

inline int findfa(int x) {
  if (x != fa[x]) return fa[x] = findfa(fa[x]);
  return x;
}

inline void add(int from, int to, int dis) {
  a[++cnt].to = to, a[cnt].nxt = head[from], a[cnt].v = dis, head[from] = cnt;
}

inline void dfs(int x, int fa) {
  for (int i = 1; i < 17; ++i) {
    if (deep[x] < (1 << i)) break;
    f[x][i] = f[f[x][i - 1]][i - 1];
    d1[x][i] = max(d1[x][i - 1], d1[f[x][i - 1]][i - 1]);
    if (d1[x][i - 1] == d1[f[x][i - 1]][i - 1]) d2[x][i] = max(d2[x][i - 1], d2[f[x][i - 1]][i - 1]);
    else {
      d2[x][i] = min(d1[x][i - 1], d1[f[x][i - 1]][i - 1]);
      d2[x][i] = max(d2[x][i - 1], d2[x][i]);
      d2[x][i] = max(d2[x][i], d2[f[x][i - 1]][i - 1]);
    }
  }
  for (int i = head[x]; i; i = a[i].nxt)
    if (a[i].to != fa) {
      f[a[i].to][0] = x, d1[a[i].to][0] = a[i].v;
      deep[a[i].to] = deep[x] + 1, dfs(a[i].to, x);
    }
}

inline int LCA(int x, int y) {
  if (deep[x] < deep[y]) swap(x, y);
  int t = deep[x] - deep[y];
  for (int i = 0; i < 17; ++i)
    if ((1 << i) & t) x = f[x][i];
  for (int i = 16; i >= 0; i--)
    if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
  if (x == y) return x;
  return f[x][0];
}

inline void cal(int x, int fa, int v) {
  int mx1 = 0, mx2 = 0;
  int t = deep[x] - deep[fa];
  for (int i = 0; i < 17; ++i)
    if ((1 << i) & t) {
      if (d1[x][i] > mx1) mx2 = mx1, mx1 = d1[x][i];
      mx2 = max(mx2, d2[x][i]); x = f[x][i];
    }
  if (mx1 != v) temp = min(temp, v - mx1);
  else temp = min(temp, v - mx2);
}

inline void solve(int t, int v) {
  int x = e[t].x, y = e[t].y, f = LCA(x, y);
  cal(x, f, v), cal(y, f, v);
}

int main() {
  freopen("2403.in", "r", stdin);
  freopen("2403.out", "w", stdout);
  scanf("%d %d", &n, &m);
  for (int i = 1; i <= m; ++i) scanf("%d %d %d", &e[i].x, &e[i].y, &e[i].d);
  for (int i = 1; i <= n; ++i) fa[i] = i;
  sort(e + 1, e + 1 + m, comp);
  for (int i = 1; i <= m; ++i) {
    int a = findfa(e[i].x);
    int b = findfa(e[i].y);
    if (a != b) {
      fa[a] = b, ans += e[i].d, e[i].bj = 1;
      add(e[i].x, e[i].y, e[i].d), add(e[i].y, e[i].x, e[i].d);
    }
  }
  dfs(1, 0);
  for (int i = 1; i <= m; ++i)
    if (!e[i].bj) solve(i, e[i].d);
  printf("%lld\n", ans + temp);
  return 0;
}

Summary

一道挺玄学的题目,是参考题解写的……

你可能感兴趣的:(树)