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

来源:互联网 发布:淘宝违规宝贝怎么删除 编辑:程序博客网 时间:2024/06/04 19:42

题目描述

传送门

题解

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

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

代码

#include<algorithm>#include<iostream>#include<cstring>#include<cstdio>using namespace std;#define LL long longconst 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、如果最大值和次大值都和当前边的边权相等,那就别理它啦。(只有一条边或者所有的边权都相等,而且题目中很良心的保证有解)

0 0
原创粉丝点击