HDU 5441 Travel(离线操作,并查集)
题目链接: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; }