NOIP2013货车运输 (最大生成树,分块求LCA)

2 篇文章 0 订阅
2 篇文章 0 订阅

A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入

第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。
接下来 m 行每行 3 个整数 x、y、z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意:x 不等于 y,两座城市之间可能有多条道路。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意:x 不等于 y。

输出

输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1。

样例输入

4 3 
1 2 4 
2 3 3 
3 1 1 
3 
1 3 
1 4 
1 3

样例输出

3
-1
3

0 < n < 10,000,0 < m < 50,000,0 < q < 30,000,0 ≤ z ≤ 100,000。


刚拿到题的时候确实很纠结,暴力的方法比较好想,就是从起点跑一次类似SPFA的算法,只不过求的值是两点间最小边的最大值。这个想法启示我们,会影响答案的是尽可能大的边,也就是说,如果有两条边在图中连接着同样的块,那么显然只需要考虑较大边就行了。再推而广之,在保证不破坏原图连通性的情况下,我们可以保存尽可能大的边,其它的边都可以尽可能的删去。

这样一来,问题就显然了:求最大生成树中两点之间路径的边中的最小值。对于求树中两点间的路径信息,可以考虑求最近公共祖先(LCA),求LCA的比较通用的在线算法是树上路径倍增,对于这道题,倍增数组中记录一个点的第2^k个祖先,和他到第2^k个祖先这段唯一路径中的边的最小值,然后先讲矮的那个点提到和另一个点同样的高度,让后两边一起爬树即可。

但是这道题N很小,只有10000,树的深度就更小了,并不需要对数级的算法。于是我就YY了个树上路径分块,就是把每一条从根到叶子的路径分成根号h个块,每个块占根号h层。和路径倍增法类似,先调整到同一深度,然后一起爬树。具体实现用两个数组,一个表示该叶子节点所属的块的顶部的节点,一个表示它到这个节点的路径中的最小边。在相距较远的时候先一个块一个块地比较猛地往上爬(虽然不如倍增爬得猛),然后再一个一个地往上爬,爬的时候更新答案即可,求一次最近公共祖先复杂度为根号h。


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
const int MAXN = 10010, MAXM = 50010;
const int INF = 1<<27;
int N, M, Q;

struct Node { 
	int to, z; 
	Node*next; 
} Edge1[MAXM*2], *ecnt1=Edge1, *adj1[MAXN]; 
void add1(int a, int b, int z) //原图中的连边
{ 
	++ecnt1; ecnt1->to = b; 
	ecnt1->z = z; ecnt1->next = adj1[a]; 
	adj1[a] = ecnt1; 
} 
int belong[MAXN]; 
struct Road {//用于kruskal的边数组
	int a, b, z;
	Road(){}
	Road(int i,int j,int k) { a=i; b=j; z=k; }
	bool operator < (const Road&t) const {
		return z > t.z;
	}
} rd[MAXM*2];
int tp, cnt; //临时记录当前块的边的数量和点的数量
bool vis[MAXN];
void dfs1(int u, int&rt) {
	vis[u] = 1;
	belong[u] = rt; ++cnt;
	for (Node*p = adj1[u]; p; p=p->next) {
		rd[++tp] = Road(u, p->to, p->z);//连通块涉及的边用于kruskal
		if (!vis[p->to]) dfs1(p->to, rt);
	}
}
  
Node Edge[MAXM*2], *ecnt=Edge, *adj[MAXN]; //存最大生成树
void add(int a, int b, int z)
{
	++ecnt; ecnt->to = b;
	ecnt->z = z; ecnt->next = adj[a];
	adj[a] = ecnt;
}
struct bcset { //bing cha set
	int tfa[MAXN];
	bcset() {
		for (int i = 1; i<MAXN; ++i) 
			tfa[i] = i;
	}
	int root(int a) {
		if (tfa[a]==a) return a;
		return tfa[a] = root(tfa[a]);
	}
	void unite(int a, int b) {
	tfa[root(a)]= root(b);
	}
} klu;
void kruskal() //最大生成树
{ 
	int i, c = 0, a, b;
	sort(rd+1, rd+tp+1);
	for (i = 1; i<=tp; ++i)
	{
		a = rd[i].a;
		b = rd[i].b;
		if (klu.root(a) != klu.root(b)) {
			klu.unite(a, b); ++c;
			add(a, b, rd[i].z); add(b, a, rd[i].z);
		}
		if (c==cnt-1) break;
	}
}

int mxdep, bdep;//最大深度和分块的深度
int fa[MAXN], dep[MAXN];
int falmt[MAXN]; //这个点到父亲那条边的限制
void dfstree(int u)
{
	if (dep[u] > mxdep) mxdep = dep[u];
	for (Node*p = adj[u]; p; p=p->next)
		if (p->to != fa[u]) {
			fa[p->to] = u;
			dep[p->to] = dep[u]+1;
			falmt[p->to] = p->z;
			dfstree(p->to);
		}
}
int blocktop[MAXN], blockmin[MAXN];//这个点所在块的顶端,到顶端的边中的最小值
void divide(int u) //路径分块
{ 
	if (dep[u] % bdep == 0) //新分出一块
		blocktop[u] = u, blockmin[u] = INF;
	else {
		blocktop[u] = blocktop[fa[u]];
		blockmin[u] = Min(blockmin[fa[u]], falmt[u]);
	}
	for (Node*p = adj[u]; p; p=p->next)
		if (p->to != fa[u])
			divide(p->to);
}
int query(int a, int b)//a,b到最近公共祖先的路径中的最小边
{
	if (belong[a] != belong[b]) return -1;
	int res = INF;
	if (dep[a]<dep[b]) { int c=a; a=b; b=c; }
	//以下两个循环将a,b调到一个高度
	while (dep[blocktop[a]] > dep[b])
	{
		res = Min(res, Min(falmt[blocktop[a]], blockmin[a]));
		a = fa[blocktop[a]];
	}
	while (dep[a] > dep[b])
		res = Min(res, falmt[a]), a = fa[a];
	//以下两个循环求最近公共祖先
	while (blocktop[a] != blocktop[b]) {
		res = Min(res, Min(blockmin[a], blockmin[b]));
		a = blocktop[a], b = blocktop[b];
		res = Min(res, Min(falmt[a], falmt[b]));
		a = fa[a], b = fa[b];
	}
	while (a != b) {
		res = Min(res, Min(falmt[a], falmt[b]));
		a = fa[a], b = fa[b];
	}
	return res;
}

void BuildTree()
{
	for (int i = 1; i<=N; ++i) {
		if (vis[i]) continue;
		tp = cnt = mxdep = 0;
		dfs1(i, i);
		kruskal();
		dfstree(i);
		bdep = sqrt(mxdep+1.5); //防止bdep为0
		divide(i);
	}
}

int main()
{
	int i, a, b, c;
	scanf("%d%d", &N, &M);
	for (i = 1; i<=M; ++i) {
		scanf("%d%d%d", &a, &b, &c);
		if (c==0) continue;
		add1(a,b,c); add1(b,a,c);
	}
	BuildTree();
	scanf("%d", &Q);
	while (Q--) {
		scanf("%d%d", &a, &b);
		printf("%d\n", query(a, b));
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值