第六届-A10-灾后重建

本文描述了Pear市在地震后进行道路修复的问题,需要计算每次特定点连通所需的最少时间。通过建立最小生成树并利用克鲁斯卡尔算法解决,同时提及了倍增法求最近公共祖先的思路,以及作者对逻辑与艺术世界交融的思考。
摘要由CSDN通过智能技术生成

灾后重建

Pear市一共有N(<=50000)个居民点,居民点之间有M(<=200000)条双向道路相连。
这些居民点两两之间都可以通过双向道路到达。这种情况一直持续到最近,一次严重的地震毁坏了全部M条道路。
震后,Pear打算修复其中一些道路,修理第i条道路需要Pi的时间。不过,Pear并不打算让全部的点连通,而是选择一些标号特殊的点让他们连通。
Pear有Q(<=50000)次询问,每次询问,他会选择所有编号在[l,r]之间,并且 编号 mod K  = C 的点,修理一些路使得它们连通。
由于所有道路的修理可以同时开工,所以完成修理的时间取决于花费时间最长的一条路,即涉及到的道路中Pi的最大值。

你能帮助Pear计算出每次询问时需要花费的最少时间么?这里询问是独立的,也就是上一个询问里的修理计划并没有付诸行动。

【输入格式】
第一行三个正整数N、M、Q,含义如题面所述。
接下来M行,每行三个正整数Xi、Yi、Pi,表示一条连接Xi和Yi的双向道路,修复需要Pi的时间。可能有自环,可能有重边。1<=Pi<=1000000。

接下来Q行,每行四个正整数Li、Ri、Ki、Ci,表示这次询问的点是[Li,Ri]区间中所有编号Mod Ki=Ci的点。保证参与询问的点至少有两个。

【输出格式】
输出Q行,每行一个正整数表示对应询问的答案。

【样例输入】
7 10 4
1 3 10
2 6 9
4 1 5
3 7 4
3 6 9
1 5 8
2 7 4
3 2 10
1 7 6
7 6 9
1 7 1 0
1 7 3 1
2 5 1 0
3 7 2 1

【样例输出】
9
6
8
8

【数据范围】
对于20%的数据,N,M,Q<=30
对于40%的数据,N,M,Q<=2000
对于100%的数据,N<=50000,M<=2*10^5,Q<=50000. Pi<=10^6. Li,Ri,Ki均在[1,N]范围内,Ci在[0,对应询问的Ki)范围内。

以下方法怕是拿不了满分。思路就是构造整张图的最小生成树,在构造的过程中每加入一条边,就去检查题目要求的特殊点是否已经完全加入最小生成树了。如果在添加了当前的边之后正好满足所有的特殊点都在最小生成树上,那这条边的权值势必就是最大的权值(因为克鲁斯卡尔算法就是将边升序排序后加入的) .
只听老师讲完了这一个方法下面的就听不下去了,之后还讲了一个倍增法求最近公共祖先的方法,我没细听,等之后要是有动力了再补上吧。一会我准备再自己看一道简单题就去学别的了。
今天的心情有点terrible。许多不顺利的小事,许多障碍存在,却让我无可奈何。计算机讲求逻辑,世界上的事情如果都像计算机这样,虽然逻辑复杂,但终有最优解,那该多好。但又觉得如果世界只剩下了逻辑,那该多么乏味。所以还是要感谢抽象思维,感谢艺术,感谢人类情感,并且我庆幸自己纠缠在逻辑世界的泥潭里,尚能无有压力地浅尝艺术之甘霖。

#include <iostream>
#include <cstdio> 
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXM = 2*10^5;
const int MAXNN = 50000+10;
int N,M,Q;
struct Edge{
	int from;
	int to;
	int cost;
	Edge(){
	}
	Edge(int from1,int to1,int cost1){
		from = from1;
		to = to1;
		cost = cost1;
	}
	bool operator < (const Edge &e)const {
		return cost < e.cost;
	}
}edges[MAXM];

//并查集
int parent[MAXNN];

void init(){
	for(int i = 0;i<=N;i++)
		parent[i] = -1;
}
int Find(int t){
	int s;
	for(s = t;parent[s] > 0;s = parent[s]);
	while(t!=s){
		int tmp = parent[t];
		parent[t] = s;
		t = tmp;
	}
	return s;
} 
int Union(int r1,int r2){
	int R1 = Find(r1);
	int R2 = Find(r2);
	if(parent[R1] < parent[R2]){
		parent[R1] = parent[R1] + parent[R2];
		parent[R2] = R1;  
		//注意以上两行代码的顺序不能颠倒 
	}else{
		parent[R2] = parent[R1] + parent[R2];
		parent[R1] = R2;
	}
}

void easy(int l,int r,int mod,int c){
	init(); //初始化并查集 
	for(int i = 0;i<M;++i){
		Edge pEdge = edges[i];
		int from = pEdge.from;
		int to = pEdge.to;
		int cost = pEdge.cost;
		
		if(Find(from) != Find(to))
			Union(from,to);
		else
			continue;
		
		//每加入一条边,就判断要求的特殊点是否已经完全连通 
		bool isok = true;
		int first = -1; //记住第一个已连通的特殊点的父亲 
		for(int j = l;j<=r;++j){
			if(j % mod == c){   //看关注的特殊点是否都已经连通 
				if(first < 0)
					first = Find(j);
				else{
					if(first != Find(j)){ //没有连通 
						isok = false; 
						break;
					}
				}
			}
		}
		//如果关注点都连通了 
		if(isok){
			printf("%d\n",cost);
			break; 
		} 
			
	}
	
}

int main(){
	scanf("%d %d %d",&N,&M,&Q);
	for(int i = 0;i<M;++i){
		int a,b,c;
		scanf("%d %d %d",&a,&b,&c);
		Edge e = Edge(a,b,c);
		edges[i] = e;
	}
	sort(edges,edges+M);
	for(int i = 0;i<Q;++i){
		int l,r,mod,c;
		scanf("%d %d %d %d",&l,&r,&mod,&c);
		easy(l,r,mod,c);
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值