[BZOJ1977][BeiJing2010组队]次小生成树 Tree(kruskal+链剖)
2016-04-03 19:43
344 查看
题目描述
传送门题解
严格的次小生成树。做法是这样的:先用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 =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 =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、如果最大值和次大值都和当前边的边权相等,那就别理它啦。(只有一条边或者所有的边权都相等,而且题目中很良心的保证有解)
相关文章推荐
- poj1961 kmp失效函数的运用
- Bash Shell脚本:输入时间,输出距离你的生日天数
- JavaScript版HashMap的简单实现——通过原型prototype扩展
- 【bzoj3165】【HEOI2013】【Segment】【线段树】
- 带权重的随机算法
- 分布式开发 (负载均衡图解)
- 解决vbe6ext.olb不能被加载 内存溢出 问题
- Hadoop 生态系统中个项目的简介
- GCD使用
- 逆元的各种求解方式
- UI基础 - UITabBarController
- 第五周进度条
- Calendar Provider
- ZooKeeper
- jquery实现ajax实例
- 小题目泛做
- php笔记之表单验证
- Gridland
- MapReduce读/写RCFile文件
- 基本的排序算法