HDU 5441 Travel(离线操作,并查集)

题目链接:HDU 5441 Travel


题目大意:

有一个n个点的无向图,给出m条边的边权,给出q次询问,每次给出一个值,求用到所有边权不大于这个值的边的情况下,能够互相到达的点对的个数(自己到自己不算)

(a,b) (b,a) 不同当且仅当a!=b


当时做的时候思路很快就想出来了,就是每次把满足条件的点分成不同的集合,每个集合的点互相可达,每个集合正好构成无向完全图,每个集合点对个数为 n * (n-1). 可就是写不出来代码 T^T...

其实就用并查集来合并就好了,就像是龙珠那道题的思想。每个集合初始值为1,合并的时候子节点加到父节点上。 

求和的时候,只去求不同集合的就行了。

用到     sum += ((num[x] + num[y]) * (num[x] + num[y] - 1) - (num[x] * (num[x]-1)) -(num[y] * (num[y]-1)));  //想想为什么

就是先直接加上(n * (n-1)),再把之前加过的去掉。


另外 ,由于数据较大,需要用到离线查询。


【源代码】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 20008;
const int maxe = 100000 + 10;
int fa[maxn],num[maxn];
int ans [5050];
int findset(int x){
	if(x == fa[x]) return x;
	else return fa[x] = findset(fa[x]);
}
struct node{
	int v1,v2,len;
	bool operator <(const node& a) const{ 
		return len<a.len;
	}
}edge[maxe];
struct node2{
	int id,val;
	bool operator <(const  node2 &a)const{
		return val<a.val;
	}
}qq[5050];
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		int n,m,q;
		scanf("%d%d%d",&n,&m,&q);
		for(int i=1;i<=n;i++){ //初始化
			fa[i] = i;
			num[i] = 1;
		}
		for(int i=0;i<m;i++){
			scanf("%d%d%d",&edge[i].v1,&edge[i].v2,&edge[i].len);
 		}
		for(int i=0;i<q;i++){
			scanf("%d",&qq[i].val);
			qq[i].id = i; //离线操作, 记录id
		}
		sort(edge,edge+m);
		sort(qq,qq+q);
		// memset(ans,0,sizeof(ans));
		int cnt = 0;	int sum = 0;	
		for(int i=0;i<q;i++){
			while(cnt<m){
				if(edge[cnt].len <= qq[i].val){
					int x = findset(edge[cnt].v1);
					int y = findset(edge[cnt].v2);
					if( x != y){
						sum += ((num[x] + num[y]) * (num[x] + num[y] - 1) - (num[x] * (num[x]-1)) -(num[y] * (num[y]-1)));
						fa[x] = y;
						num[y] += num[x]; //加到父亲结点上
						// cout<<sum<<"ff"<<endl;
					}
					cnt++;
				}
				else break;
			}
			ans[qq[i].id] = sum;
		}
		for(int i=0;i<q;i++){
			printf("%d\n",ans[i]);
		}
	}
	return 0;
}


posted @ 2015-09-15 14:59  编程菌  阅读(138)  评论(0编辑  收藏  举报