垃圾佬的旅游II-并查集

垃圾佬的旅游II

TimeLimit:3000MS  MemoryLimit:128MB
64-bit integer IO format: %lld
已解决 |  点击收藏
Problem Description

继上次的长期旅行,这回垃圾佬要和静静去双人旅行了。

他们一共想要游览n个景点。

景点之间交通非常发达,景点之间一共有m条可行的双向路。

但是要知道,静静是会晕车的,看到她难受,垃圾佬是会心疼的。


已知有n个景点,景点间有m条公路,每条公路有一个w,表示在该在公路上航行时静静的晕车值。

旅行时,垃圾佬会带着静静尽量选择晕车值最小的路线进行旅行。

但是,静静也是有一定忍耐限度的。如果晕车值超过了她的忍耐限度,垃圾佬会果断决定放弃这条路线。


现在,垃圾佬想进行Q次询问,给定静静的晕车值k,问有多少对景点(x,y)间会存在可行的旅行路线(如果(x,y)和(y,z)可行,则(x,z)可行,也就是说连通性是可传递的)

Input

只有一组数据

第1行三个正整数n,m,Q,含义如题目描述。

接下来m行,每行3个正整数a,b,w,表示a号景点到b号景点之间有一条晕车值为w的双向公路。

接来下Q行,每行一个正整数k,表示静静的忍耐度。

0<=n<=100000,0<=m<=200000,0<=Q<=200000。其他数不超过1e9。


Output

共Q行,对于每个询问做出回答。

SampleInput
 
  
5 5 2
1 2 1
2 3 2
3 4 1
4 5 4
5 1 1
1
2
SampleOutput
 
  
4
10
样例解释:
第一个询问:(1,2),(1,5),(2,5),(3,4)。
其中(2,5)的具体走法为2-1-5。
第二个询问:(1,2),(1,3),(1,4),(1,5),(2,3),(2,4),(2,5),(3,4),(3,5),(4,5)。
其中,(4,5)的具体走法为4-3-2-1-5

解题思路:离线操作+并查集,对询问和边都按从小到大进行排序,边从小到大开始遍历,如果边的权值(晕车度)小于当前的边就合并该边的两个点(如果两个点不在一个集合内),否则就将当前记录的点对数加入该询问的答案,直到询问结束或边结束。

合并两个集合的时候增加的点对的个数等于两集合内数目的乘积。

下面是代码及注释

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 100500,maxm =200500,maxq =200500;
int father[maxn],num[maxn];
long long ans = 0;   //统计有多少对的计数器
struct edge
{
    int x,y,v;
} E[maxm];
struct Answer
{
    int No,q;
    long long answer;
} Q[maxq];
bool cmp1(edge A,edge B)
{
    return A.v<B.v;
}
bool cmp2(Answer A,Answer B)
{
    return A.q<B.q;
}
bool cmp3(Answer A,Answer B)
{
    return A.No<B.No;
}
void Make_set(int n) /*建立一个并查集*/
{
    for(int i=1; i<=n; i++)
    {
        father[i]=i;
        num[i]=1;
    }
}
int Find(int x)      /*寻找x的根(非递归)*/
{
    int r=x;
    while(father[r]!=r)
        r=father[r];
    int i=x,j;
    while(i!=r)
    {
        j=father[i];
        father[i]=r;
        i=j;
    }
    return r;
}
void Union(int x,int y)   /*普通版合并*/
{
    int fx=Find(x),fy=Find(y);
    ans = ans + num[fx]*num[fy];   //合并并计算点对
    if(fx!=fy)
    {
        num[fx]=num[fy]=num[fx]+num[fy];
        father[fx]=fy;
    }
}
int main()
{
    int n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    for(int i=0; i<m; i++)scanf("%d%d%d",&E[i].x,&E[i].y,&E[i].v);
    for(int i=0; i<q; i++)scanf("%d",&Q[i].q),Q[i].No=i;
    sort(E,E+m,cmp1);         //将边按晕车值从小到大排序
    sort(Q,Q+q,cmp2);         //将询问按忍耐度从小到大排序
    Make_set(n);              //初始化并查集
    int now_edge = 0;         //代表当前是第几条边
    for(int i=0; i<q; i++)
    {
        while(now_edge!=m)    //如果没有边了就不进行判断了
        {
            if(E[now_edge].v<=Q[i].q)  //如果当前边的晕车值小于当前询问的忍耐值
            {
                if(Find(E[now_edge].x)!=Find(E[now_edge].y))
                    //如果两个点不在一个集合内合并两个集合
                    Union(E[now_edge].x,E[now_edge].y);
                now_edge++;             //下一条边
            }
            else
                break;
        }
        Q[i].answer=ans;               //记录下当前答案
    }
    sort(Q,Q+q,cmp3);                  //再按原来输入的顺序进行排序并输出
    for(int i=0;i<q;i++)printf("%lld\n",Q[i].answer);
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值