LightOJ 1101 A Secret Mission

http://www.lightoj.com/volume_showproblem.php?problem=1101

给一个无向图,询问两点间路径的最小危险度。某条路径的危险度定义为路径上的最大边权值。


第一步:

由于边权值很小,是1~1000,所以一开始的思路是先把权值为1的边都加进来,看哪些询问的两点已经连起来了,那些询问的回答就是1;再把权值为2的边加进来……依次下去直到所有询问回答完毕。如此的复杂度是1000*Q=5*10^7,加了各种优化后还是要超时的。

在这个思路上花了很多时间,但也不是没有一点意义。因为在加边的时候发现,如果某条边的两点已经并起来了就没必要加这条边了,如此就可以想到最小生成树。于是就可以把原图转化为一棵树,那两节点间只有一条路径,感觉稍微简单点了,问题也转化成了求这条路径上的最大权值。


第二步:

一看到树自然而然想到LCA,但这不像路径求和那样可以sum[u]+sum[v]-2*sum[LCA],这个最大值不能做减法操作,那如何在知道了两个节点以及他们的LCA以后快速求出结果呢?(做完后看别人代码发现个非常简便有用的方法,见最后的红字)感觉好像只能暴力,但想到了如果某个询问已经解决了,而另一个询问中两点的LCA是这个询问中LCA的父节点,感觉只要稍稍修改下就能求出解了。于是就想到了先解决LCA在较深层次的询问,比如第x层;然后再把连接x-1和x层的边加进来,就可以解决LCA在第x-1层的询问。

把思路再想的具体点就是:将询问按照LCA由深到浅排序,将所有节点也按由深到浅排序。先将最深一层的节点所引出的边加进来并啊并的(并的时候维护这个节点到祖先节点的路径上的最大值),如此以后如果某询问的LCA在这一层,那他的两点就肯定刚并起来,可以解决了。

每条边扫了一遍,每个询问也扫了一遍,复杂度大概就是E+Q,几十万的样子,反而还是前面的排序占了主要时间。


PS:中间由于把节点搞成从0开始,结果RE了几次,以为是堆栈溢出,就把树的深搜写成非递归了。每个节点要加入那个rmq的数组只在两种情况:1、第一次搜到;2、某个子节点搜完。第一种情况好搞,第二种要略有修改,所以具体做法如下:

放入堆栈的元素要增加两个记录:1、父节点的标号,2、此节点是否访问过。第一次在堆栈中用到某节点时将访问标记置1,但不要把它拿出来,同时往数组中加入他的标号。第二次用到时,也就是访问标记已经为1了,说明他后面都搜完了,那不要再扫他子节点了,把他拿出来同时把他的父节点标号放入数组就行了。


知道节点和LCA后最朴素的方法就是暴力向上找,其实可以仿造RMQ的常规解法Sparse Table去优化……第一次看见这种用法,觉得自己的思想如同黄昏后的贫瘠之地伴随着凄凉的号角声吟唱着我的忧郁。常规的Sparse Table里num[i][j]表示从i开始长度为2^j的区间,那照搬到树结构里:P[i][j]表示从节点i开始向上走2^j步所到的节点(RMQ里根据ij可以找到区间的另一端,但这里不好找,所以得保存一下),再搞个一样的数组保存这个区间的信息。

于是P[i][0]就是结点i的父节点。其他的么:P[i][j]=P[P[i][j-1][j-1];(从i点走2^(j-1)步到点i',再从i'走2^(j-1)步)。

然后向上走走就自由发挥了。其实如此一来LCA都不用找了,实在方便不少。


总结:虽然是树形结构,其实每一个结点往上走都是一个一维的数组,竟然没想到。。。。。。

代码:

#include <set>
#include <map>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <queue>
#include <stack>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#define ll __int64
//#define ll long long
#define PI acos(-1.0)
#define PRN 105
using namespace std;
int f_min(int x,int y){return x<y?x:y;}
int f_max(int x,int y){return x>y?x:y;}
int f_abs(int x){return x<0?-x:x;}
int prime[PRN],Pn;
bool unprime[PRN];
void get_prime(){
	int i,j,lim=sqrt(PRN);
	memset(unprime,0,sizeof(unprime));Pn=0;
	for(i=2;i<PRN;i++){
		if(unprime[i])continue;
		prime[Pn++]=i;
		if(i>lim)continue;
		for(j=i*i;j<PRN;j+=i)unprime[j]=1;
	}
}
struct Edge{
	int u,v,w,next;
	bool mark;
	bool friend operator<(Edge a,Edge b){
		return a.w<b.w;
	}
};
struct Node{
	int dep,fa,prev;
};
Node node[50005];
Edge edge[210000],inie[100000];
int head[50005],en;
void insert(int u,int v,int w){
	edge[en].u=u;edge[en].v=v;edge[en].w=w;
	edge[en].mark=0;
	edge[en].next=head[u];head[u]=en++;
}
int N,M,Q;
void get_data(){
	scanf("%d%d",&N,&M);
	int i,u,v,w;
	for(i=0;i<M;i++){
		scanf("%d%d%d",&u,&v,&w);u--;v--;
		inie[i].u=u;
		inie[i].v=v;
		inie[i].w=w;
	}
}
int fa[50005];
int findf(int x){
	if(fa[x]!=x)fa[x]=findf(fa[x]);
	return fa[x];
}
void get_edge(){
	memset(head,-1,sizeof(head));en=0;
	sort(inie,inie+M);
	int i,u,v;
	for(i=0;i<N;i++)fa[i]=i;
	for(i=0;i<M;i++){
		u=findf(inie[i].u);
		v=findf(inie[i].v);
		if(u==v)continue;
		fa[u]=v;
		insert(u,v,inie[i].w);
		insert(v,u,inie[i].w);
	}
}
int maxd;
void dfs(int u,int dep){
//	printf("%d\n",u);
	node[u].dep=dep;maxd=f_max(maxd,dep);
	int i,v;
	for(i=head[u];i!=-1;i=edge[i].next){
		if(edge[i].mark)continue;
		edge[i^1].mark=1;
		v=edge[i].v;
		node[v].fa=u;
		node[v].prev=edge[i].w;
		dfs(v,dep+1);
	}
}
void get_tree(){
	node[0].fa=-1;
	node[0].prev=0;
	maxd=0;
	dfs(0,0);
}
int P[50005][20],vmax[50005][20];
void pre_process(){
	memset(P,-1,sizeof(P));
	memset(vmax,0,sizeof(vmax));
	int i,j,l;
	for(i=0;i<N;i++){
		P[i][0]=node[i].fa;
		vmax[i][0]=node[i].prev;
	}
	for(j=1,l=2;l<=maxd;j++,l<<=1){
		for(i=0;i<N;i++){
			if(l<=node[i].dep){
				P[i][j]=P[P[i][j-1]][j-1];
				vmax[i][j]=f_max(vmax[i][j-1],vmax[P[i][j-1]][j-1]);
			}
		}
	}
}
void ans(int u,int v){
	int res=0,i;
	if(node[u].dep<node[v].dep)swap(u,v);
	i=20;
	while(node[u].dep>node[v].dep){
		while(node[u].dep-(1<<i)<node[v].dep)i--;
		res=f_max(res,vmax[u][i]);
		u=P[u][i];
	}
	if(u==v){printf("%d\n",res);return;}
	i=20;
	while(P[u][0]!=P[v][0]){
		while(node[u].dep<(1<<i)||P[u][i]==P[v][i])i--;
		res=f_max(res,f_max(vmax[u][i],vmax[v][i]));
		u=P[u][i];
		v=P[v][i];
	}
	res=f_max(res,f_max(vmax[u][0],vmax[v][0]));
	printf("%d\n",res);
}
void run(){
	get_edge();
	get_tree();
	pre_process();
	scanf("%d",&Q);
	int i,u,v;
	for(i=0;i<Q;i++){
		scanf("%d%d",&u,&v);u--;v--;
		ans(u,v);
	}
}
int main(){
	int i,t;
	scanf("%d",&t);
	for(i=1;i<=t;i++){
		get_data();
		printf("Case %d:\n",i);
		run();
	}
	return 0;
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值