BZOJ 1977([BeiJing2010组队]次小生成树 Tree-LCA的位运算)

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

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 1176   Solved: 234
[ 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

这题的关键就在于求Lca,记录路径上的最小与严格次小值.

用f[i][j]表示i的第2^j个儿子(0 表示 不存在)

那么f[i][j]=f[ f[i][ j-1] ][j-1]

dp[i][j]和dp0[i][j]表示点i到f[i][j]的最小和严格次小值(不存在=-1),那么只需特判即可.

int lca(int x,int y,int &nowdp,int &nowdp0)
{
	if (deep[x]<deep[y]) swap(x,y);
	int t=deep[x]-deep[y]; //差的数量
	for (int i=0;t;i++) 
		if (t&bin[i])   //转化为位运算 bin[i]表示2<<i 把t看成2进制
		{
			x=f[x][i];
			t-=bin[i];
		}
	int i=Li-1; //Li 表示 最高存到2^(Li-1)个父亲
	while (x^y) //x和y不相等时
	{
		while (f[x][i]==f[y][i]&&i) i--; //当i==0时只能向上跳
		x=f[x][i];y=f[y][i];
	}
}
程序:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
#define MAXN (100000+10)
#define MAXM (600000+10)
#define Li (17)
#define INF (2000000000)
int edge[MAXM],pre[MAXM],weight[MAXM],next[MAXM],size=0;
int addedge(int u,int v,int w)
{
	edge[++size]=v;
	weight[size]=w;
	next[size]=pre[u];
	pre[u]=size;
}
int addedge2(int u,int v,int w)
{
	addedge(u,v,w);
	addedge(v,u,w);
}
int f[MAXN][Li]={0},dp[MAXN][Li]={0},dp0[MAXN][Li]={0},deep[MAXN],n,m;
struct E
{
	int u,v,w;
	friend bool operator<(E a,E b){return a.w<b.w;	}
}e[MAXM];
bool b[MAXM],vis[MAXN];
int queue[MAXN],head,tail;
void bfs()
{
	memset(vis,0,sizeof(vis));
	head=tail=1;queue[1]=1;vis[1]=1;deep[1]=0;
	while (head<=tail)
	{
		int &u=queue[head];
		if (u!=1)
		{
			for (int i=1;i<17;i++)
			{
				if (f[u][i-1])
				{
					f[u][i]=f[f[u][i-1]][i-1];
				}
				if (f[u][i]==0) break;
				if (f[u][i])
				{
					dp[u][i]=max(dp[u][i-1],dp[f[u][i-1]][i-1]);
				}
				if (i==1)
				{
					if (dp[u][0]!=dp[f[u][0]][0]) dp0[u][1]=min(dp[u][0],dp[f[u][0]][0]);
					else dp0[u][1]=-1;
				}
				else
				{
					dp0[u][i]=max(dp0[u][i-1],dp0[f[u][i-1]][i-1]);
					if (dp[u][i-1]!=dp[f[u][i-1]][i-1]) dp0[u][i]=max(dp0[u][i],min(dp[u][i-1],dp[f[u][i-1]][i-1]));
				}
			}
		}
		for (int p=pre[u];p;p=next[p])
		{
			int &v=edge[p];
			if (!vis[v])
			{
				queue[++tail]=v;
				vis[v]=1;deep[v]=deep[u]+1;
				f[v][0]=u;dp[v][0]=weight[p];dp0[v][0]=-1;
			}		
		}	
		head++;
	}
}
int bin[Li];
void check(int &nowdp,int &nowdp0,int c)
{
	if (c<=nowdp0) return;
	else if (nowdp0<c&&c<nowdp) nowdp0=c;
	else  if (c==nowdp) return;
	else if (nowdp<c) {nowdp0=nowdp;nowdp=c;}
}
int lca(int x,int y,int &nowdp,int &nowdp0)
{
	nowdp=nowdp0=-1;
	if (deep[x]<deep[y]) swap(x,y);
	int t=deep[x]-deep[y];
	for (int i=0;t;i++) 
		if (t&bin[i])
		{
			check(nowdp,nowdp0,dp[x][i]);
			check(nowdp,nowdp0,dp0[x][i]);
			x=f[x][i];
			t-=bin[i];
		}
	int i=Li-1;
	while (x^y)
	{
		while (f[x][i]==f[y][i]&&i) i--;
		check(nowdp,nowdp0,dp[x][i]);
		check(nowdp,nowdp0,dp0[x][i]);
		check(nowdp,nowdp0,dp[y][i]);
		check(nowdp,nowdp0,dp0[y][i]);
		x=f[x][i];y=f[y][i];
	}
}
int father[MAXN];
long long sum_edge=0;
int getfather(int x)
{
	if (father[x]==x) return x;
	father[x]=getfather(father[x]);
	return father[x];
}
void union2(int x,int y)
{
	father[father[x]]=father[father[y]];
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) father[i]=i;
	memset(b,0,sizeof(b));
	memset(next,0,sizeof(next));
	for (int i=0;i<Li;i++) bin[i]=1<<i;
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
	}
	sort(e+1,e+1+m);
	for (int i=1;i<=m;i++)
	{
		if (getfather(e[i].u)!=getfather(e[i].v)) {union2(e[i].u,e[i].v);addedge2(e[i].u,e[i].v,e[i].w);sum_edge+=e[i].w;	}
		else b[i]=1;
	}
	bfs();
	
	long long mindec=-1;
	for (int i=1;i<=m;i++)
		if (b[i])
		{
			int nowdp,nowdp0;
			lca(e[i].u,e[i].v,nowdp,nowdp0);
			if (nowdp==e[i].w) nowdp=nowdp0;
			if (nowdp==-1) continue;
			if (mindec==-1||mindec>e[i].w-nowdp) mindec=e[i].w-nowdp; 
		}
	printf("%lld\n",sum_edge+mindec);
	return 0;
}






你可能感兴趣的:(BZOJ 1977([BeiJing2010组队]次小生成树 Tree-LCA的位运算))