题意:
给出一个n个点、m条边的无向图,边上有权值,有q组询问,每组询问给出一个数字x,我们要在图中找出‘点对’的个数,这些‘点对’(例如a,b)满足从a到b有一条路径经过的每一条边都要小于x,输出每组询问点对的数量。
思路:
并查集。
我们把查询操作离线化,把边按权值从小到大排序,把查询操作也从小到大排序,每次查询操作都扫一遍边,判断满足的边的端点在不在同一个并查集中,如果不在就合并这两个并查集,同时记录新增加的数量,扫到不满足的边就跳出,继续下一次查询操作。
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct Edge
{
int u,v,x;
}e[100005];
int cmp(Edge a,Edge b)
{
return a.x<b.x;
}
struct Q
{
int pos,x;
}query[5005];
int cmp1(Q a,Q b)
{
return a.x<b.x;
}
int ans[5005];
int fa[200005];
int num[200005];
int n,m,q;
void init()
{
for(int i=0;i<=n;i++)
{
fa[i]=i;
num[i]=1;
}
return ;
}
int find1(int x)
{
return fa[x]==x? x: fa[x]=find1(fa[x]);
}
void union1(int x,int y)
{
x=find1(x);
y=find1(y);
if(y<x)
swap(x,y);
fa[y]=x;
num[x]+=num[y];
return ;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&n,&m,&q);
for(int i=0;i<m;i++)
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].x);
init();
for(int i=0;i<q;i++)
{
scanf("%d",&query[i].x);
query[i].pos=i;
}
sort(e,e+m,cmp);
sort(query,query+q,cmp1);
int now=0;
int tmp=0;
for(int i=0;i<q;i++)
{
// printf("now=%d\n",now);
int j;
for(j=now;j<m;j++)
{
if(e[j].x>query[i].x)
break;
int fax=find1(e[j].u);
int fay=find1(e[j].v);
//printf("fax=%d fay=%d\n",fax,fay);
if(fax==fay)
continue;
else
{
int tmp1=(num[fax]+num[fay])*(num[fax]+num[fay]-1);
int tmp2=(num[fax]-1)*num[fax];
int tmp3=(num[fay]-1)*num[fay];
tmp+=(tmp1-tmp2-tmp3);
union1(fax,fay);
}
}
now=j;
ans[query[i].pos]=tmp;
}
for(int i=0;i<q;i++)
printf("%d\n",ans[i]);
}
return 0;
}