货车运输,洛谷之提高历练地,倍增

正题

       第一题:货车运输

       这道题很经典啊~~

       直接建立倍增关系式求LCA即可。

       不妨设i的第2^j个爸爸是f[i][j],而这条路径上的最小值设为mmin[i][j]。

       f[i][j]=f[f[i][j-1](i的2^(j-1)次方爸爸)][j-1]的第j-1次方个爸爸。

       那么mmin[i][j]=min(mmin[i][j-1],mmin[f[i][j-1]][j-1]);

       所以说我们先把当前的x和y跳到同一层,然后再一起往上跳。

       这题那么经典肯定有坑点咯~

       1.要建立最大生成树后求解

       2.要考虑在不同树的情况

代码<带讲解>

#include
#include
#include
#include
#include
using namespace std;

struct edge{
	int y,c,next;
}s[100010];
struct used{
	int x,y,c;
}p[50010];
int n,m;
int len=0;
int first[10010];
int f[10010];
int fa[10010][21];
int mmin[10010][21];
int dep[10010];

int findpa(int x){
	if(f[x]!=x) return f[x]=findpa(f[x]);
	return x;
}

bool cmp(used x,used y){
	return x.c>y.c;
}

void ins(int x,int y,int c){
	len++;
	s[len].y=y;s[len].c=c;s[len].next=first[x];first[x]=len;
}

void dfs(int x){//dfs找以x根的子树集合
	for(int i=first[x];i!=0;i=s[i].next){
		int y=s[i].y;
		if(y!=fa[x][0]){//有边并且不是爸爸
			fa[y][0]=x;//更新他的爸爸
			dep[y]=dep[x]+1;//深度加一
			mmin[y][0]=s[i].c;//最小值更新
			dfs(y);
		}
	}
}

int find_min(int x,int y){
	int nowmin=1e9;
	if(dep[x]>dep[y]){//优先把y放在底层
		int t=x;x=y;y=t;
	}
	for(int i=20;i>=0;i--)//让y往上跳到与x同样高度
		if(dep[fa[y][i]]>=dep[x]) {//可以跳就跳
			nowmin=min(nowmin,mmin[y][i]);//更新min
			y=fa[y][i];//更新位置
		}
	if(x==y) return nowmin;
	for(int i=20;i>=0;i--)
		if(fa[x][i]!=fa[y][i]){//父亲不一样才能跳,要调到lca(x,y)的下一层(lca是最近公共祖先)
			nowmin=min(nowmin,min(mmin[x][i],mmin[y][i]));
			x=fa[x][i],y=fa[y][i];
		}
	nowmin=min(nowmin,min(mmin[x][0],mmin[y][0]));//再往上跳一步即可
	return nowmin;
}

int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y,c;
		scanf("%d %d %d",&x,&y,&c);
		if(x==y) continue;
		p[i].x=x;p[i].y=y;p[i].c=c;//存边,x,y,c
	}
	sort(p+1,p+1+m,cmp);//按边权从大到小排序
	for(int i=1;i<=n;i++)
		f[i]=i;//并查集维护当前点所在集合
	for(int i=1;i<=m;i++){
		int fx=findpa(p[i].x),fy=findpa(p[i].y);//找当前集合祖先
		if(fx!=fy){//不再同一集合
			f[fx]=fy;
			ins(p[i].x,p[i].y,p[i].c);//加边并且把两个集合合并一起。
			ins(p[i].y,p[i].x,p[i].c);
		}
	}
	for(int i=1;i<=n;i++){
		if(dep[i]!=0) continue;//被遍历过就选下一个。
		dep[i]=1;//没有被遍历过就当根。
		dfs(i);//深搜
		fa[i][0]=i;//更新父节点(无用)
		mmin[i][0]=1e9;//更新最小值(无用)
	}
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n;i++){
			fa[i][j]=fa[fa[i][j-1]][j-1];
			mmin[i][j]=min(mmin[i][j-1],mmin[fa[i][j-1]][j-1]);
		}
	int x,y;
	int k;
	scanf("%d",&k);
	for(int i=1;i<=k;i++){
		scanf("%d %d",&x,&y);
		if(findpa(x)!=findpa(y))//不在同一个集合,那就输出-1(不可能到达)
			printf("-1\n");
		else 
			printf("%d\n",find_min(x,y));//如果不是那就输出路径上最小值
	}
}

你可能感兴趣的:(货车运输,洛谷之提高历练地,倍增)