[BZOJ1977][BeiJing2010组队]次小生成树 Tree(kruskal+链剖)

题目描述

传送门

题解

严格的次小生成树。做法是这样的:
先用kruskal求出最小生成树;
对于不在最小生成树里的每一条边,求两个端点所在树链权值的最大值和次大值;
用当前边的权值减去最大值(如果当前边的权值等于最大值的话就用次大值),每次更新最小增量;
用最小生成树的权值和加上最小增量即为答案。
最大值和次大值可以用线段树维护,链剖查询,当然也可以写倍增。

那么为什么是这样呢?
感受一下。。。
求出最小生成树之后我们实际上得到了一棵树(这不废话。。。),然后枚举每一条不在树里的边,如果加上这条边树里就出现了环(这不又废话。。。),那么我们为了不出现环就要在这个环里删去一条边,除去枚举的边,剩下的边其实就组成了枚举的边的两个顶点之间的一条树链。
为什么要维护最大值和次大值呢?因为这道题是严格的次小生成树,所以不能使删去的边和枚举的边权值相等。
至于为什么树链中的边都小于等于枚举的边呢?很显然如果大于的话你求的还是最小生成树吗?。。。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define LL long long

const int max_n=2e5+5;
const int max_m=3e5+5;
const int max_e=max_n*2;
const int max_tree=max_n*5;
const int INF=2e9;

int n,m,cnt,cnt1,N,u,t;
int f[max_n];
int tot,point[max_n],next[max_e],v[max_e],c[max_e];
int size[max_n],h[max_n],father[max_n],son[max_n],faedge[max_n],sonedge[max_n];
int top[max_n],num[max_e],val[max_n];
int maxn[max_tree],_maxn[max_tree];
int a[10];

struct hp{
    int maxn,_maxn;
};
struct hq{
    int u,t,w,pd;
};
hq edge[max_m];

LL Min,Minadd,add;

inline int cmp(hq a,hq b){
    return a.w<b.w;
}

inline int find(int x){
    if (f[x]==x) return x;
    f[x]=find(f[x]);
    return f[x];
}
inline void merge(int x,int y){
    int f1=find(x); int f2=find(y);
    f[f1]=f2;
}

inline void addedge(int x,int y,int z){
    ++tot; next[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
    ++tot; next[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z;
}
inline void dfs_1(int x,int fa,int dep){
    size[x]=1; h[x]=dep; father[x]=fa;
    int maxson=0;
    for (int i=point[x];i;i=next[i])
      if (v[i]!=fa){
        faedge[v[i]]=i;
        dfs_1(v[i],x,dep+1);
        size[x]+=size[v[i]];
        if (size[v[i]]>maxson){
            maxson=size[v[i]];
            son[x]=v[i];
            sonedge[x]=i;
        }
      }
}
inline void dfs_2(int x,int fa){
    if (son[fa]!=x) top[x]=x;
    else top[x]=top[fa];
    if (son[x]){
        num[sonedge[x]]=++N;
        val[N]=c[sonedge[x]];
        dfs_2(son[x],x);
    }
    for (int i=point[x];i;i=next[i])
      if (v[i]!=fa&&v[i]!=son[x]){
        num[i]=++N;
        val[N]=c[i];
        dfs_2(v[i],x);
      }
}

inline void update(int now){
    maxn[now]=_maxn[now]=-1;
    a[1]=maxn[now<<1]; a[2]=maxn[now<<1|1];
    a[3]=_maxn[now<<1]; a[4]=_maxn[now<<1|1];
    if (a[1]==a[2]&&a[2]==a[3]&&a[3]==a[4])
        maxn[now]=_maxn[now]=a[4];
    else{
        sort(a+1,a+5);
        maxn[now]=a[4];
        for (int i=3;i>=1;--i)
          if (a[i]!=a[i+1]){
            _maxn[now]=a[i];
            break;
          }
    }
}
inline void build(int now,int l,int r){
    int mid=(l+r)>>1;
    if (l==r){
        maxn[now]=val[l];
        _maxn[now]=val[l];
        return;
    }
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    update(now);
}
inline hp query(int now,int l,int r,int lrange,int rrange){
    int mid=(l+r)>>1;
    hp ans;
    hp ans1,ans2;
    if (lrange<=l&&r<=rrange)
      return ans=(hp){maxn[now],_maxn[now]};

    bool pd1=false,pd2=false;
    if (lrange<=mid)
      ans1=query(now<<1,l,mid,lrange,rrange),pd1=true;
    if (mid+1<=rrange)
      ans2=query(now<<1|1,mid+1,r,lrange,rrange),pd2=true;

    if (!pd1&&!pd2) return ans=(hp){0,0};
    if (!pd1) return ans=(hp){ans2.maxn,ans2._maxn};
    if (!pd2) return ans=(hp){ans1.maxn,ans1._maxn};

    int MAXN=-1,_MAXN=-1;
    a[1]=ans1.maxn,a[2]=ans1._maxn,a[3]=ans2.maxn,a[4]=ans2._maxn;
    if (a[1]==a[2]&&a[2]==a[3]&&a[3]==a[4])
        MAXN=_MAXN=a[4];
    else{
        sort(a+1,a+5);
        MAXN=a[4];
        for (int i=3;i>=1;--i)
          if (a[i]!=a[i+1]){
            _MAXN=a[i];
            break;
          }
    }

    return ans=(hp){MAXN,_MAXN};
}

inline hp ask(int u,int t){
    int f1=top[u],f2=top[t];
    int MAXN=-1,_MAXN=-1;
    hp ans;
    while (f1!=f2){
        if (h[f1]<h[f2]){
            swap(f1,f2);
            swap(u,t);
        }

        hp k=query(1,1,N,num[faedge[f1]],num[faedge[u]]);
        if (MAXN==-1&&_MAXN==-1){
            MAXN=k.maxn; _MAXN=k._maxn;
        }
        else{
            a[1]=k.maxn,a[2]=k._maxn,a[3]=MAXN,a[4]=_MAXN;
            if (a[1]==a[2]&&a[2]==a[3]&&a[3]==a[4]){
                MAXN=_MAXN=a[4];
            } 
            else{
                sort(a+1,a+5);
                MAXN=a[4];
                for (int i=3;i>=1;--i)
                  if (a[i]!=a[i+1]){
                    _MAXN=a[i];
                    break;
                  }

            }
        }
        u=father[f1];
        f1=top[u];      
    }
    if (u==t) return ans=(hp){MAXN,_MAXN};
    if (h[u]>h[t]) swap(u,t);

    hp k=query(1,1,N,num[sonedge[u]],num[faedge[t]]);
    if (MAXN==-1&&_MAXN==-1){
        MAXN=k.maxn; _MAXN=k._maxn;
    }
    else{
        a[1]=k.maxn,a[2]=k._maxn,a[3]=MAXN,a[4]=_MAXN;
        if (a[1]==a[2]&&a[2]==a[3]&&a[3]==a[4]){
            MAXN=_MAXN=a[4];
        } 
        else{
            sort(a+1,a+5);
            MAXN=a[4];
            for (int i=3;i>=1;--i)
              if (a[i]!=a[i+1]){
                _MAXN=a[i];
                break;
              }
        }
    }   

    return ans=(hp){MAXN,_MAXN};
}

int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;++i)
      scanf("%d%d%d",&edge[i].u,&edge[i].t,&edge[i].w);
    sort(edge+1,edge+m+1,cmp);
    for (int i=1;i<=n;++i)
      f[i]=i;
    for (int i=1;i<=m;++i){
        if (find(edge[i].u)!=find(edge[i].t)){
            merge(edge[i].u,edge[i].t);
            Min+=edge[i].w;
            edge[i].pd=true;
            ++cnt;
            if (cnt==n-1) break;
        }
    }

    for (int i=1;i<=m;++i)
      if (edge[i].pd)
        addedge(edge[i].u,edge[i].t,edge[i].w);
    dfs_1(1,0,1);
    dfs_2(1,0);
    build(1,1,N);

    cnt1=0;
    Minadd=INF;
    for (int i=m;i>=1;--i)
      if (!edge[i].pd){
        u=edge[i].u; t=edge[i].t;
        hp ans=ask(u,t);
        add=ans.maxn;
        if (add==edge[i].w) add=ans._maxn;
        if (add!=edge[i].w) Minadd=min(Minadd,edge[i].w-add);
        ++cnt1;
        if (cnt1==m-cnt) break;
      }
    Min+=Minadd;
    printf("%lld\n",Min);
}

总结

错误记录:
1、谁告诉你n个点都有边连着啦?它自己呆着不行啊?这种情况下最小生成树的边也不一定是n-1(写数据生成器的时候发现的)
2、如果最大值和次大值都和当前边的边权相等,那就别理它啦。(只有一条边或者所有的边权都相等,而且题目中很良心的保证有解)

你可能感兴趣的:(kruskal,bzoj,链剖)