传送门
严格的次小生成树。做法是这样的:
先用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、如果最大值和次大值都和当前边的边权相等,那就别理它啦。(只有一条边或者所有的边权都相等,而且题目中很良心的保证有解)