bzoj 1977: [BeiJing2010组队]次小生成树 Tree

20 篇文章 0 订阅

1977: [BeiJing2010组队]次小生成树 Tree

Time Limit: 10 Sec   Memory Limit: 512 MB
Submit: 2638   Solved: 648
[ Submit][ Status][ Discuss]

Description

小 C 最近学了很多最小生成树的算法,Prim 算法、Kurskal 算法、消圈算法等等。 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说: 如果最小生成树选择的边集是 EM,严格次小生成树选择的边集是 ES,那么需要满足:(value(e) 表示边 e的权值)  这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

Input

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

Output

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

Sample Input

5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6

Sample Output

11

HINT

数据中无向图无自环; 50% 的数据N≤2 000 M≤3 000; 80% 的数据N≤50 000 M≤100 000; 100% 的数据N≤100 000 M≤300 000 ,边权值非负且不超过 10^9 。

Source

题解:首先需要构造出最小生成树(kruskal)算法。然后从不是最小生成树的边中选出一条,如果把这条边加入树中,那么在树中必然形成了一个环,用lca 可以确定出有这条边的两个顶点构成的树链,然后求一下树链的最大值和次大值(在建树的时候用倍增预处理出最大值和次大值)

因为是严格的次小生成树,所以如果新加的边的权值等于删去的边的权值是不合法的,所以我们维护最大值和次大值

当当前边的边权与树链中的最大值相等时,我们用次大值来更新最小增量。那么有没有可能树链中的最大值大于当前边的边权呢?其实不必担心因为这是不可能的,在构建最小生成树时是按权值排序的,如果当前边的边权小于树链中的最大值的话,那么建树时一定会选用当前边,所以我们要求次小生成树,其实就是求最小增量,而这种方式用心感受一下,还是很科学的。

当然这道题貌似也可以用线段树+链剖来做,有想法的可以尝试(貌似代码巨长)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100003
using namespace std;
int n,m;
struct data
{
 int x,y,v,pd;
};data a[3*N];
int father[N],tot,mn;
int next[N*2],point[N],v[2*N],c[2*N],deep[N];
int fa[N][20],maxn[N][20],mx[N][20],mi[20];
long long ans=0;
int cmp(data x,data y)
{
 return x.v<y.v;
}
void add(int x,int y,int k)
{
 tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=k;
 tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=k;	
}
int find(int x)
{
 if (father[x]==x) return x;
 father[x]=find(father[x]);
 return father[x];
}
void dfs(int x,int f,int depth)
{
 deep[x]=depth;
 for (int i=1;i<=17;i++)
 {
  if (deep[x]-mi[i]<0) break;
  fa[x][i]=fa[fa[x][i-1]][i-1];
  maxn[x][i]=max(maxn[x][i-1],maxn[fa[x][i-1]][i-1]);
  if (maxn[x][i-1]==maxn[fa[x][i-1]][i-1])
   mx[x][i]=max(mx[x][i-1],mx[fa[x][i-1]][i-1]);
  else
  {
   int t=min(maxn[x][i-1],maxn[fa[x][i-1]][i-1]);
   t=max(t,mx[x][i-1]); t=max(t,mx[fa[x][i-1]][i-1]);
   mx[x][i]=t;
  }
 }
 for (int i=point[x];i;i=next[i])
 if (v[i]!=f)
 {
  fa[v[i]][0]=x;
  maxn[v[i]][0]=c[i];
  mx[v[i]][0]=-1;
  dfs(v[i],x,depth+1);
 }
}
int lca(int x,int y)
{
 if (deep[x]<deep[y]) swap(x,y);
 int k=deep[x]-deep[y];
 for (int i=0;i<=17;i++)
 if (k>>i&1) x=fa[x][i];
 if (x==y) return x;
 for (int i=17;i>=0;i--)
 if (fa[x][i]!=fa[y][i])
 x=fa[x][i],y=fa[y][i];
 return fa[x][0];
}
void solve(int x,int f,int v)
{
 int k=deep[x]-deep[f];
 int p1=0,p2=0;
 for (int i=0;i<=17;i++)
  if (k>>i&1)
  {
   p2=max(p2,mx[x][i]);
   if (maxn[x][i]>p1)
   {
   	 p2=max(p2,p1);
   	 p1=maxn[x][i]; 
   }
  }
 if (p1==v) mn=min(mn,v-p2);
 else mn=min(mn,v-p1);
}
int main()
{
 scanf("%d%d",&n,&m);
 for (int i=1;i<=m;i++)
  scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].v);
 sort(a+1,a+m+1,cmp);
 for (int i=1;i<=n;i++) father[i]=i;
 int num=0;
 mi[0]=1;
 for (int i=1;i<=17;i++) mi[i]=mi[i-1]*2;
 for (int i=1;i<=m;i++)
 {
  int r1=find(a[i].x); int r2=find(a[i].y);
  if (r1!=r2)
  {
   father[r2]=r1;
   ans+=(long long)a[i].v;
   a[i].pd=1;
   add(a[i].x,a[i].y,a[i].v);
   num++;
  }
  if (num==n-1) break;
 }
 dfs(1,0,1);
 mn=1e9;
 for (int i=1;i<=m;i++)
 if (!a[i].pd)
 {
  int t=lca(a[i].x,a[i].y);
  solve(a[i].x,t,a[i].v);
  solve(a[i].y,t,a[i].v);
 }
 ans+=(long long)mn;
 printf("%lld\n",ans);
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值