您的位置:首页 > 其它

HDU 5441 Travel (并查集)

2015-09-16 19:45 344 查看

题目大意

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

分析

给m条边的权值和查询的值都从小到大排序

然后对于每个查询,把不大于这个值的边加入到并查集中去,并记录第一个比查询值大的边的标号,下次查询从此标号开始即可。因此,每条边只需遍历一次。

sum[i]表示以节点i为根的子树拥有根节点的个数,因此对于每个查询的答案为

ans += (sum[x] + sum[y] ) * (sum[x] + sum[y] - 1) - sum[x] * (sum[x] - 1) + sum[y] * (sum[y] - 1);


代码

#include <iostream>
#include <algorithm>
#include <cstdio>

using namespace std;
const int maxn = 20010;
struct Edge {
int from , to , v;
}edge[maxn * 5];
struct Node {
int id , v;
}Que[maxn];
//给edge的v从小到大排序
bool cmp1(Edge const &A , Edge const &B) {
return A.v < B.v;
}
//给Que的v从小到大排序
bool cmp2(Node const &A , Node const &B) {
return A.v < B.v;
}

int pa[maxn] , sum[maxn]; //pa[i]表示节点i的父亲 , sum[i]表示以i为根节点的的节点数
int findset(int x) {return pa[x] != x ? pa[x] = findset(pa[x]) : x;}

int ret[maxn];
int main()
{
int t , m , n , q;
scanf("%d" , &t);
while(t--)
{
scanf("%d%d%d" , &n , &m , &q);
for(int i = 0; i < m; i++) scanf("%d%d%d" , &edge[i].from , &edge[i].to , &edge[i].v);
for(int i = 0; i < q; i++) {scanf("%d" , &Que[i].v); Que[i].id = i;}
sort(edge , edge + m , cmp1);
sort(Que , Que + q , cmp2);

//初始化并查集
for(int i = 1; i <= n; i++) {sum[i] = 1; pa[i] = i;}

//一共只循环了m次
int cur = 0 , ans = 0;
for(int i = 0; i < q; i++) {
for(int j = cur; j < m; j++) {
if(edge[j].v <= Que[i].v) {
int x = findset(edge[j].from) , y = findset(edge[j].to);
if(x != y) {
ans -= sum[x] * (sum[x] - 1) + sum[y] * (sum[y] - 1);
sum[y] += sum[x];
pa[x] = y;
ans += sum[y] * (sum[y] - 1);
}
}
else {cur = j; break;}
}
ret[Que[i].id] = ans;
}
for(int i = 0; i < q; i++) printf("%d\n" , ret[i]);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  并查集