BZOJ 1977 Tree 次小生成树 (kruskal st表 倍增lca)

来源:互联网 发布:兼职seo软文代写 编辑:程序博客网 时间:2024/05/19 03:44

1977: [BeiJing2010组队]次小生成树 Tree

Time Limit: 10 Sec Memory Limit: 512 MB
Submit: 3121 Solved: 791
[Submit][Status][Discuss]
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 。

思路:
要求次小生成树,肯定先要求最小生成树,考虑到次小生成树肯定是最小生成树更换一条边而得(加上一条边后图中肯定会出现一个环,用这条边替换掉其余边中最大的一条),所以枚举每条不在最小生成树上的边(u, v),因为是严格次小,所以求u和v路径上的最大边权和次大边权。
如果最大边权和(u, v)的边权相等,那么减去次大边的边权,加上(u, v)的边权,更新答案。
如果最大边权比(u, v)的边权要小,那么减去最大边的边权,加上(u, v)的边权,更新答案。
用st表记录max [ x ][ i ]表示x的从x向上走2^i步所经过路径上的最大值、次大值就可以做到O(nlogn)。

#include <cstdio>  #include <cstring>  #include <iostream>  #include <algorithm>#define LL long longusing namespace std;  const int maxn=100010, maxm=300010, maxk=22;const int INF = 1e9+7; int n, m, delta, idc;int head[maxn], acc[maxn][maxk], dep[maxn], fat[maxn], fst[maxn][maxk], snd[maxn][maxk];LL ans;  bool in[maxm];  struct Edge{    int x, y, v;}ed[maxm];  struct Edge1{    int to, next, w;}ed1[maxm];bool cmp(Edge a, Edge b){    return a.v < b.v;}   void adde(int u, int v, int w){//记录x->y的路径     ed1[++idc].to = v;    ed1[idc].next = head[u];    head[u] = idc;    ed1[idc].w = w;}  int findfa(int x){//并查集     return fat[x]==x ? x:fat[x]=findfa(fat[x]);}  void dfs(int u){//初始化st表     for(int i=1; i<=20; i++){          acc[u][i] = acc[acc[u][i-1]][i-1];//维护到达位置         int no1 = fst[u][i-1], no2 = fst[acc[u][i-1]][i-1];          fst[u][i] = max(no1, no2);//维护最长边的边长         snd[u][i] = max(snd[u][i-1], snd[acc[u][i-1]][i-1]);        if(no1 != no2) snd[u][i] = max(snd[u][i], min(no1, no2));//维护次长边的边长     }      for(int k=head[u]; k; k=ed1[k].next){        int v = ed1[k].to;        if(v != acc[u][0]){            dep[v] = dep[u] + 1;            acc[v][0] = u;              fst[v][0] = ed1[k].w;            dfs( v );          }      }}  void kruskal(){      sort(ed+1, ed+1+m, cmp);    int cnt = 0;      for(int i=1; i<=n; i++) fat[i] = i;//初始化father      for(int i=1; i<=m; i++){        if(cnt == n-1) break;//完成         int u = ed[i].x, v = ed[i].y, w = ed[i].v;          if(findfa(u) == findfa(v)) continue;          cnt++;        fat[findfa(u)] = findfa(v);        in[i] = 1;//记录它是否在最小生成树中         adde(u, v, w);        adde(v, u, w);//构造最小生成树,保存两点间路径         ans += w;    }  }  int lca(int u, int v){//倍增求lca     if(dep[u] < dep[v]) swap(u, v);      int h = dep[u] - dep[v];    for(int i=20; i>=0 && h; i--)        if( h & (1<<i) ) u = acc[u][i];//跳到同一深度     if(u == v) return u;    for(int i=20; i>=0; i--)        if(acc[u][i] != acc[v][i]){            u = acc[u][i];            v = acc[v][i];          }    return acc[u][0];  }  void query(int u, int lc, int w){      int max1 = 0, max2 = 0;      for(int i=20,h=dep[u]-dep[lc]; i>=0; i--)         if( h & (1<<i) ){            if(fst[u][i] > max1){                max2 = max1;                max1 = fst[u][i];               }            max2 = max(max2, snd[u][i]);            h -= (1<<i);          }      if(w == max1) delta = min(delta, w-max2);      else delta = min(delta, w-max1);  }void solve(int x){      int u = ed[x].x, v = ed[x].y, w = ed[x].v, lc = lca(u, v);      query(u, lc, w); query(v, lc, w);//在整条链上维护最长路与新边的差的最小值(相等的话就是次小边) }  int main(){    delta = INF;    scanf("%d%d", &n, &m);      for (int i=1; i<=m; i++){        int x, y, z;        scanf("%d%d%d", &x, &y, &z);        ed[i] = (Edge){x, y, z};    }     kruskal();    dfs( 1 );      for(int i=1; i<=m; i++)        if ( !in[i] ) solve( i );    printf("%lld\n", ans+delta);      return 0;  }  
阅读全文
0 0