题目链接:HDU 5441 Travel
题目大意:
有一个n个点的无向图,给出m条边的边权,给出q次询问,每次给出一个值,求用到所有边权不大于这个值的边的情况下,能够互相到达的点对的个数(自己到自己不算)
(a,b) (b,a) 不同当且仅当a!=b
当时做的时候思路很快就想出来了,就是每次把满足条件的点分成不同的集合,每个集合的点互相可达,每个集合正好构成无向完全图,每个集合点对个数为 n * (n-1). 可就是写不出来代码 T^T...
其实就用并查集来合并就好了,就像是龙珠那道题的思想。每个集合初始值为1,合并的时候子节点加到父节点上。
求和的时候,只去求不同集合的就行了。
用到 sum += ((num[x] + num[y]) * (num[x] + num[y] - 1) - (num[x] * (num[x]-1)) -(num[y] * (num[y]-1))); //想想为什么
就是先直接加上(n * (n-1)),再把之前加过的去掉。
另外 ,由于数据较大,需要用到离线查询。
【源代码】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 20008;
const int maxe = 100000 + 10;
int fa[maxn],num[maxn];
int ans [5050];
int findset(int x){
if(x == fa[x]) return x;
else return fa[x] = findset(fa[x]);
}
struct node{
int v1,v2,len;
bool operator <(const node& a) const{
return len<a.len;
}
}edge[maxe];
struct node2{
int id,val;
bool operator <(const node2 &a)const{
return val<a.val;
}
}qq[5050];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++){ //初始化
fa[i] = i;
num[i] = 1;
}
for(int i=0;i<m;i++){
scanf("%d%d%d",&edge[i].v1,&edge[i].v2,&edge[i].len);
}
for(int i=0;i<q;i++){
scanf("%d",&qq[i].val);
qq[i].id = i; //离线操作, 记录id
}
sort(edge,edge+m);
sort(qq,qq+q);
// memset(ans,0,sizeof(ans));
int cnt = 0; int sum = 0;
for(int i=0;i<q;i++){
while(cnt<m){
if(edge[cnt].len <= qq[i].val){
int x = findset(edge[cnt].v1);
int y = findset(edge[cnt].v2);
if( x != y){
sum += ((num[x] + num[y]) * (num[x] + num[y] - 1) - (num[x] * (num[x]-1)) -(num[y] * (num[y]-1)));
fa[x] = y;
num[y] += num[x]; //加到父亲结点上
// cout<<sum<<"ff"<<endl;
}
cnt++;
}
else break;
}
ans[qq[i].id] = sum;
}
for(int i=0;i<q;i++){
printf("%d\n",ans[i]);
}
}
return 0;
}