垃圾佬的旅游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;
}