HDU-3938 Portal(离线并查集)

时间:2021-09-16 11:11:38

先上原题:

ProblemDescription

ZLGG found a magic theory that the bigger banana the bigger bananapeel .This important theory can help him make a portal in our universal.Unfortunately, making a pair of portals will cost min{T} energies. T in a pathbetween point V and point U is the length of the longest edge in the path.There may be lots of paths between two points. Now ZLGG owned L energies and hewant to know how many kind of path he could make.

Input

There are multiple test cases. The first line of input containsthree integer N, M and Q (1 < N ≤ 10,000, 0 < M ≤ 50,000, 0 < Q ≤ 10,000). N is the number of points, M is the number of edges and Qis the number of queries. Each of the next M lines contains three integers a,b, and c (1 ≤ a, b ≤ N, 0 ≤ c ≤ 10^8) describing an edge connecting the point a and b with cost c.Each of the following Q lines contain a single integer L (0 ≤ L ≤ 10^8).

Output

Output the answer to each query on a separate line.

SampleInput

10 10 10

7 2 1

6 8 3

4 5 8

5 8 2

2 8 9

6 4 5

2 1 5

8 10 5

7 3 7

7 8 8

10

6

1

5

9

1

8

2

7

6

SampleOutput

36

13

1

13

36

1

36

2

16

13

这个题的大意就不翻译了,总之就是问在条件限定内能在图上生成的无向路径总条数,限制条件是给出的值不能超过路径中最长边的值。(注意题目暗含条件:图中不会有环,因为如果有环,那么当路经过环时可能会在环上转无限圈,生成的路径自然会有无数种,这时此题会无解)

看完第一反应是并查集,但是感觉这个并查集与普通的直接判断点之间是否有关系不一样。而且题目的最终要求是求出一个路径的数目,与点之间是否有关系联系不大,所以应该是一个加了条件制约的并查集。

由于输入是无序的,而且查询有很多次并且也无序,一次一次查肯定会爆掉(天真的我最早就想这样),所以只能使用离线做法,一次求解所有答案。

首先将边和查询请求都读入,将边按照权重排序,将查询按照限制条件的大小排序(读入时记得要将读取查询的顺序记录下来,否则出答案的时候就0.0了)

然后从小到大依次读入边进行合并,合并的过程中完成对路径总数的统计。由于难以遇到与查询点相同的权值,所以这里要将每一次总路径增加前的总路径数进行记录,然后如果刚并入的边权值超出了本次查询的限制,那么上一次路径增加后的结果就是本次查询的结果。

还有一个难以理解的点就是关于边的统计,这个困扰了我很久(2333)。

首先各个图的边的数目要存在各个图构成的集合里,存在哪里——放在并查集的根节点里;

第二就是合并后边的数量统计的问题。我最初想最初各个节点都是一个集合,边数都是0,然后一条边把两个集合合并起来,把两个集合的边数求和后,边数再+1……但是这样做又出现了路径数的计算的新问题。如果有一个集合的边数为0,另一个边数不为0,那总边数想要只+1就要多写几行代码限制一下……太费劲了……

后来无奈之下参考了别人的题解(http://blog.csdn.net/weiguang_123/article/details/8067239,感谢这位博主),我才明白可以用这么一种提前记录的方法来记录边数。把每个点的初始边数都设为1,即假定会有一条边来连接这个点;然后在连接两个集合的时候直接把两个集合的乘积加到路径数统计里,再把两个集合里的边数合并就行了(这样原本两个集合都预先多记录了1条边,但是在连接的时候多出一条连接用的边,抵消了1条边的预先记录【算进了乘积里】,这样新集合里仍然是预先多记录一条边)。

然后就是要注意,有的查询限制是小于最短边的(即答案为0),有的查询限制是大于全图最长边的(即答案是代码中并查集结束后的cnt2代表的最新统计值),然后输出的时候因为我们当初记录了读入顺序,所以存储答案的时候按照原来读入的顺序存储就行了。

说了这么多,日后自己也应该能看懂了。接下来是代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
//#include <cmath>
using namespace std;
// root_path means pre-root-path for next step, can avoid invalid 0s
struct eage{
long long power;
int u;
int v;
int kinds;
}eages[50001], eages_used[50001];

struct query{
long long value;
int id;
}querys[10001], query_after_sort[10001];

int n,m,q;
int union_find_set[10001], answer[10001];
long long root_path[10001];

int comparator(const void*a, const void*b){
const eage*elem1 = (const eage*)a;
const eage*elem2 = (const eage*)b;
return elem1->power-elem2->power;
}

int comparator2(const void*a, const void*b){
const query*elem1 = (const query*)a;
const query*elem2 = (const query*)b;
if(elem1->value != elem2->value)return elem1->value-elem2->value;
return elem1->id-elem2->id;
}

int findf(int x){
while(x != union_find_set[x]){
x = union_find_set[x];
}
return x;
}

int main(){
while(scanf("%d%d%d",&n,&m,&q)!=EOF){
for(int i=0; i<10001; i++)root_path[i]=1,union_find_set[i]=i;
for(int i=0; i<m; i++){
scanf("%d%d%I64d",&(eages[i].u), &(eages[i].v), &(eages[i].power));
eages[i].kinds=1;
}
for(int i=0; i<q; i++){
scanf("%I64d", &(querys[i].value));
querys[i].id=i;
}
qsort(eages, m, sizeof(eage), comparator);
qsort(querys, q, sizeof(query), comparator2);
int cnt1=0, cnt2=0;
int anscnt=0;
for(int i=0; i<m; i++){ //处理查询限制比全图最小边还小的情况
if(querys[i].value<eages[0].power)answer[querys[i].id]=0, anscnt++;
else break;
}
for(int i=0; i<m; i++){ //并查集代码
int fu = findf(eages[i].u);
int fv = findf(eages[i].v);
//root_path[fu]++;
if(fu == fv)continue; //由于考虑到可能会有退化输入,所以在这里过滤一下

cnt1 = cnt2; //记录上一次的统计结果

cnt2 += root_path[fu]*root_path[fv]; //把合并后新增的路径数加进统计结果中
root_path[fu] += root_path[fv]; //合并后拥有的边数+1条预统计边数
union_find_set[fv] = fu;

while(eages[i].power>querys[anscnt].value){ //判断并更新在本条边与上条之间的查询限制答案
answer[querys[anscnt].id] = cnt1;
anscnt++;
}
}
for(int i=anscnt; i<q; i++){ //处理比全图最大边还大的情况
answer[querys[i].id] = cnt2;
}
for(int i=0; i<q; i++){
printf("%d\n",answer[i]);
}
}
return 0;
}