zoukankan      html  css  js  c++  java
  • 严格次小生成树

    题目描述

    小C最近学了很多最小生成树的算法,Prim算法、Kurskal算法、消圈算法等等。正当小C洋洋得意之时,小P又来泼小C冷水了。小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的。

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

    输入格式

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

    输出格式

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

    洛谷P4180

    分析:看到这个题的第一眼想到的是枚举,把所有生成树枚举一遍求最小的,但是显然不行,因为数据有些大了。

    想一下最小生成树的算法?Kurskal是通过每一次加最小边来实现最小树,所以我们考虑一下边。

    对于一棵最小生成树而言,删去树上一边,再加上一条边维护树,就有可能构成次小生成树,为保证它是严格的,所以还要再判断这条边与删去的边是否相等。

    现在我们考虑删边,如何保证删去边再加上边仍然是树呢?显然先加边,然后看这条边与其他边构成的环中,最大的那条边是多少,为什么要用最大边呢,因为要求的是次小生成树,在一个点附近,加完边后剩下的边一定大于等于加上的边,不然根据Kurskal不会把这条边加上,但这条边还有可能和加的边相等,所以在记录最大边的同时还要记录次大边。

    删边和加边的问题解决了,接下来就是记录了,关于记录,使用lca是我没有想到的。

    为什么要用lca呢?加边后构成的环,不考虑加的这条边的话,可以分成两部分,一是从边的from到lca,二是从边的to到lca,所以在跑倍增lca的同时(顺便)维护一下最大值和次大值。

    最后就是计算了,计算的时候也是同上分两部分,记得判断最大值是不是和边权一样,如果是的话要用次大值。

      1 #include<cstdio>
      2 #include<cstring>
      3 #include<algorithm>
      4 using namespace std;
      5 const int N=1e5+10;
      6 //前向星存边
      7 struct Edge{
      8     int to,from,next,val;
      9     bool isin;
     10     Edge(){isin=val=next=0;}
     11     bool operator < (const Edge &A)const {
     12         return val<A.val;
     13     }
     14 }e[N<<1],E[N*3];
     15 int len,Head[N];
     16 void Ins(int a,int b,int c){
     17     e[++len].to=b;e[len].val=c;
     18     e[len].next=Head[a];Head[a]=len;
     19 }
     20 //并查集+Kurskal
     21 int f[N],m,n;
     22 int find(int x){
     23     return f[x]==x?x:(f[x]=find(f[x]));
     24 }
     25 int ans=0x3f3f3f3f;long long tot=0;
     26 void Krs(){
     27     int cnt=1;
     28     sort(E+1,E+m+1);
     29     for(int i=1;cnt<n;i++){
     30         int v=E[i].to,u=E[i].from;
     31         if(find(v)!=find(u)){
     32             cnt++;
     33             E[i].isin=1;
     34             tot+=E[i].val;//计算最小生成树
     35             Ins(u,v,E[i].val);
     36             Ins(v,u,E[i].val);
     37             f[find(v)]=find(u);
     38         }
     39     }
     40 }
     41 //倍增lca板子
     42 int dep[N],p[N][20],Max[N][20],Smax[N][20];
     43 void dfs(int x){
     44     for(int i=0;p[x][i];i++){
     45         p[x][i+1]=p[p[x][i]][i];
     46         Max[x][i+1]=max(Max[x][i],Max[p[x][i]][i]);
     47         //注意求次大的时候看看两段的最大值是不是相等,如果不判断的话
     48         //在最大值相等的时候,次大值会被更新为最大值
     49         if(Max[x][i]==Max[p[x][i]][i])    
     50             Smax[x][i+1]=max(Smax[x][i],Smax[p[x][i]][i]);
     51         else 
     52             Smax[x][i+1]=max(min(Max[x][i],Max[p[x][i]][i]),
     53                             max(Smax[x][i],Smax[p[x][i]][i]));
     54     }
     55     for(int i=Head[x];i;i=e[i].next){
     56         int v=e[i].to;
     57         if(v!=p[x][0]){
     58             dep[v]=dep[x]+1;
     59             Max[v][0]=e[i].val;
     60             Smax[v][0]=-1;
     61             p[v][0]=x;
     62             dfs(v);
     63         }
     64     }
     65 }
     66 //这段和lca板子一模一样
     67 int lca(int a,int b){
     68     if(dep[a]<dep[b])swap(a,b);
     69     int d=dep[a]-dep[b];
     70     for(int i=0;d;i++,d>>=1)
     71         if(d&1)a=p[a][i];
     72     if(a==b)return a;
     73     for(int i=18;i>=0;i--)
     74         if(p[a][i]!=p[b][i])
     75             a=p[a][i],b=p[b][i];
     76     return p[a][0];
     77 }
     78 //计算
     79 void calc(int u,int v,int w){
     80     int d=dep[u]-dep[v];//深度差,判断路径
     81     int m1=0,m2=0;
     82     for(int i=0;d;i++,d>>=1){
     83         if(d&1){//如果可以往上跳
     84             m2=max(m2,Smax[u][i]);//细节,先求次大值
     85             if(Max[u][i]>m1){//如果更新最大值,那么原来的最大值m1可能为新的次大值
     86                 m2=max(m2,m1);//判断次大值是否更新
     87                 m1=Max[u][i];//更新最大值
     88             }
     89         }
     90     }
     91     if(m1==w)ans=min(ans,w-m2);//如果最大值与边相等,用次大值更新
     92     else ans=min(ans,w-m1);//否则用最大值
     93 }
     94 int main(){
     95 //    freopen("a.txt","r",stdin);
     96     scanf("%d%d",&n,&m);
     97     for(int i=1;i<=n;i++)
     98         f[i]=i;
     99     for(int i=1;i<=m;i++)
    100         scanf("%d%d%d",&E[i].from,&E[i].to,&E[i].val);
    101     Krs();
    102     dfs(1);
    103     for(int i=1;i<=m;i++){
    104         if(!E[i].isin){
    105             int u=E[i].from,v=E[i].to,Lca;
    106             Lca=lca(u,v);
    107             calc(u,Lca,E[i].val);
    108             calc(v,Lca,E[i].val);
    109         }
    110     }
    111     printf("%lld
    ",tot+ans);
    112 }
  • 相关阅读:
    书写神器——markdown
    数据大作战
    Laraveladmin自定义拓展及常见问题(下)
    Laraveladmin自定义拓展及常见问题(上)
    打造网页版聊天页面的几个知识点
    说说windows系统的事儿
    《软件开发者路线路》读后感
    如何优雅的仿站Step One——扒网站篇
    SQLSERVER 独占锁 独占模式
    解决vs2010调试很慢的方法
  • 原文地址:https://www.cnblogs.com/anyixing-fly/p/12411799.html
Copyright © 2011-2022 走看看