HDU 5441 Travel (并查集)

来源:互联网 发布:炒股软件公式编写 编辑:程序博客网 时间:2024/04/29 16:31

题目大意

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;}
0 0
原创粉丝点击