【图论专题五】雪之国度

【NOIP2016提高A组集训第17场11.16】雪之国度
(File IO): input:city.in output:city.out
Time Limits: 2000 ms Memory Limits: 524288 KB Detailed Limits

Description
雪之国度有N座城市,依次编号为1到N,又有M条道路连接了其中的城市,每一条道路都连接了不同的2个城市,任何两座不同的城市之间可能不止一条道路。雪之女王赋予了每一座城市不同的能量,其中第i座城市被赋予的能量为Wi。
如果城市u和v之间有一条道路,那么只要此刻雪之女王的能量不小于|Wu-Wv|,这条道路就是安全的。如果城市u和v之间存在两条没有重复道路的安全路径(其中每一段道路都是安全的),则认为这两座城市之间有着良好的贸易关系。
最近,雪之女王因为情感问题,她的能量产生巨大的波动。为了维持雪之国度的经济贸易,她希望你能帮忙对Q对城市进行调查。对于第j对城市uj和vj,她希望知道在保证这两座城市之间有着良好贸易关系的前提之下,自己最少需要保持多少的能量。

Input
每一组数据第一行有3个整数,依次为N,M,Q,表示城市个数,道路个数,和所需要进行的调查次数。
之后一行,有N个整数,依次为每一个城市被赋予的能量Wi。
之后M行,每一行有2个整数,表示对应编号的两个城市之间有一条道路。
之后Q行,每一行有2个整数,表示一组调查的城市目标。
(保证图联通!)

Output
输出一共有Q行,依次对应Q次调查的结果。其中第j行给出了第j次调查的结果,即雪之女王需要保持的最少能量值。如果永远也无法做到,输出"infinitely"。

Sample Input
7 8 4
3 2 4 1 3 5 9
1 2
1 3
2 4
2 5
3 6
6 7
4 6
5 6
4 5
4 6
5 6
2 7

Sample Output
4
4
2
infinitely

Data Constraint
对于20%的数据来说,3<=N<=10, 3<=M<=20, 1<=Q<=10
对于另30%的数据来说,Wi=0
对于100%的数据来说,3<=N<=100000, 3<=M<=500000, 1<=Q<=100000, 每一座城市的能量Wi满足0<=Wi<=200000.

题解

题目大意就是让你找到一个边最大值最小的环,并且这个环包括xi,yi。
一开始用了好多种方法。一开始先是找到一个最小生成树,因为最小的边一定是在最小生成树上的。所以我们将最小生成树删掉之后,剩下的边再做一次最小生成树。但是,我发现这样子做是有问题的。因为对于一个询问,他在最小生成树上经过的边仅仅只是两个点到他们的LCA,而并不是所有的树边。这样子做答案会错的很离谱。
那么,怎么做呢?

正解

我们可以思考一下,发现对于一条树边,那么他边上的权值是没有用的,而有用的权值仅仅只是在非树边上。这时,我们思考如何处理这些非树边。
很显然,一条非树边(x,y)的贡献是对于树的路径x-lca(x,y)-y这一条路可以被非树边x-y替代。这里就出现了一个环。因为环上的任意两个点的路径有至少2条。所以如果询问(x1,y1),在x1到y1的路径上出现了这个环时,我们就可以用这个环上的其他路径替代,而这时的贡献就是那条非树边(当然,如果有多条非树边可以构成环,那么肯定取最小的)。
基于这个思想,我们可以想到一个会超时的方法:现将原先树边的权值全部变为无穷大。对于一条非树边(x,y)将x-y在树上的路径的权值全部对这条非树边的权值取一个min。然后对于询问(x1,y1)。就直接在树上找从x1->y1的最大边权就好了。
现在的问题是,如何快速给x-y在树上的路径的权值取min.方法有很多,我也不阻拦你打一个树链剖分.
我们从小到大填入权值.这时我们发现,边的条数不多,所以暴力填边会有很多重复的操作。所以我们可以用并查集维护一下。
这样的时间复杂度:O(nlogn+m)

代码

#include<cstdio>
#include<cstring>
#define N 800031
#define M 800031
#define K 21
using namespace std;
int a[N],b[N],c[N],fa[N][K],c1[N][K],next[M],head[M],edge[M],deep[N],father[N],w[N];
bool bz[M];
int total1,n,m,q;
int max(int x,int y)
{
	if (x<y) return y;
	else	return x;
}
int min(int x,int y)
{
	if (x>y) return y;
	else	return x;
}
int getfather(int x)
{
	if (father[x]==x) return x;
	father[x]=getfather(father[x]);
	return father[x];
}
void insert(int x,int y)
{
	total1++;next[total1]=head[x];head[x]=total1;edge[total1]=y;
}
int abs(int x)
{
	if (x>0) return x;
	else	return -x;
}
void swap(int &x,int &y)
{
	int t=x;x=y;y=t;
}
void qsort(int l,int r)
{
	int i=l,j=r,mid=c[(i+j)/2];
	while (i<=j)
	{
		while (c[i]<mid) i++;
		while (c[j]>mid) j--;
		if (i<=j)
		{
			swap(a[i],a[j]);
			swap(b[i],b[j]);
			swap(c[i],c[j]);
			i++;
			j--;
		}
	}
	if (i<r) qsort(i,r);
	if (l<j) qsort(l,j);
}
void dg(int k,int ba)
{
	for (int i=head[k];i;i=next[i])
	{
		int y=edge[i];
		if (y==ba) continue;
		fa[y][0]=k;
		deep[y]=deep[k]+1;
		dg(y,k);
	}
}
void find(int k,int mu,int z)
{
	int lastk=0;
	while (deep[k]>deep[mu])
	{
		int u=getfather(k);
		father[lastk]=u;
		if (deep[u]<=deep[mu]) break;
		if (c1[u][0]>=999999)
			c1[u][0]=z;
		lastk=k;
		k=fa[u][0];
	}
	int u=getfather(k);
	father[lastk]=k;
}
int lca(int x,int y)
{
	if (deep[x]<deep[y]) swap(x,y);
	for (int i=20;i>=0;i--)
		if (deep[fa[x][i]]>=deep[y])
			x=fa[x][i];
	if (x==y) return x;
	for (int i=20;i>=0;i--)
		if (fa[x][i]!=fa[y][i])
		{
			x=fa[x][i];y=fa[y][i];
		}
	return fa[x][0];
}
int main()
{
	freopen("city.in","r",stdin);
	freopen("city.out","w",stdout);
	scanf("%d%d%d",&n,&m,&q);
	for (int i=1;i<=n;i++) scanf("%d",&w[i]);
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&a[i],&b[i]);
		c[i]=abs(w[a[i]]-w[b[i]]);
	}
	qsort(1,m);
	for (int i=1;i<=m;i++) father[i]=i;
	memset(bz,false,sizeof(bz));
	for (int i=1;i<=m;i++)
	{
		int u=getfather(a[i]);
		int v=getfather(b[i]);
		if (u!=v)
		{
			father[u]=v;
			bz[i]=true;
		}
	}
	for (int i=1;i<=m;i++)
		if (bz[i]) 
		{
			insert(a[i],b[i]);
			insert(b[i],a[i]);
		//	printf("%d %d %d\n",a[i],b[i],c[i]);
		}
	dg(1,0);
	for (int i=1;i<=20;i++)
		for (int j=1;j<=n;j++)
			fa[j][i]=fa[fa[j][i-1]][i-1];
	for (int i=1;i<=n;i++) father[i]=i;
	for (int i=1;i<=n;i++) 
		for (int j=0;j<=20;j++) c1[i][j]=99999999;
	for (int i=1;i<=m;i++)
		if (!bz[i])
		{
	//		printf("%d %d %d\n",a[i],b[i],c[i]);
			int kp=lca(a[i],b[i]);
			find(a[i],kp,c[i]);
			find(b[i],kp,c[i]);
		}
	for (int i=1;i<=20;i++)
		for (int j=1;j<=n;j++)
			if (c1[fa[j][i-1]][i-1]>=9999999) c1[j][i]=c1[j][i-1];
			else
				c1[j][i]=max(c1[j][i-1],c1[fa[j][i-1]][i-1]);
	for (int times=1;times<=q;times++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		int kp=lca(u,v);
		int ans=0;
		for (int i=20;i>=0;i--)
		{
			if (deep[fa[u][i]]>=deep[kp]&&fa[u][i]!=0)
			{
				ans=max(ans,c1[u][i]);
				u=fa[u][i];
			}
			if (deep[fa[v][i]]>=deep[kp]&&fa[v][i]!=0)
			{
				ans=max(ans,c1[v][i]);
				v=fa[v][i];
			}
		}
		if (ans>=9999999) printf("infinitely\n");
		else	printf("%d\n",ans);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值