題意:
給出一張帶邊權的無向圖,q次查詢,每次查詢給出一權值,問保留圖中小於等於該權值的邊同時去除其他邊後所產生的能相互到達的點對數(x,y)和(y,x)屬於不同情況。
思路:
首先我們知道對於一個聯通塊來說,其中上述點對數就等於該聯通塊中節點的數目C(n,2)==n*(n-1)/2*2。所以可以考慮統計當前限制條件下各個聯通塊中點的數目,到這裏我們會想到dfs劃塊和並查集兩種操作。但又考慮到如果對於每次查詢均重新建圖,5*5000*1e5已經顯然超時,所以我們需要只建一次圖,並尋找一種能動態(一邊建圖一邊查各塊中點的數目)查詢各聯通塊中節點數目的方案,所以肯定就是並查集了,爲每個聯通塊的代表元添加一個表示當前節點數目的秩,合併聯通塊時很容易更新這個秩大小,同時爲了只建一次圖就算出所有答案,我們可以離線處理所有查詢並對其排序,同樣也對所有邊按邊權排序。這樣複雜度可以近似爲5*(5000+1e5)。
AC代碼:
/** Wjunjie **/
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include<algorithm>
#include <set>
#include <queue>
#include <stack>
#include<vector>
#include<map>
#include<ctime>
#define LL long long
using namespace std;
const int N=2e4+100;
const int M=100000+100;
const int mod=1e8+7;
struct query
{
int index,v;
LL res;
}que[N];
bool cmp1(query aa,query bb)
{
return aa.v<bb.v;
}
bool cmp2(query aa,query bb)
{
return aa.index<bb.index;
}
struct edge
{
int l,r,v;
bool operator <(const edge & obj)const
{
return v<obj.v;
}
}e[M];
int fat[N];
int num[N];
int find(int x)
{
if(x==fat[x])return x;
return fat[x]=find(fat[x]);
}
void unionn(int fa,int fb)//啓發式合併,可能數據小看不出來?
{
if(num[fa]<num[fb])
{
fat[fa]=fb;
num[fb]=num[fa]+num[fb];
}
else
{
fat[fb]=fa;
num[fa]=num[fa]+num[fb];
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
memset(e,0,sizeof(e));
memset(que,0,sizeof(que));
memset(num,0,sizeof(num));
memset(fat,0,sizeof(fat));
for(int i=0;i<=n;++i)fat[i]=i,num[i]=1;
for(int i=1;i<=m;++i)scanf("%d%d%d",&e[i].l,&e[i].r,&e[i].v);
for(int i=1;i<=q;++i)scanf("%d",&que[i].v),que[i].index=i;
sort(e+1,e+1+m);
sort(que+1,que+1+q,cmp1);
int cnt=1;
for(int i=1;i<=q;++i)
{
LL sum=0;
while(e[cnt].v<=que[i].v&&cnt<=m)//別忘了防止溢出
{
int fa=find(e[cnt].l),fb=find(e[cnt].r);
if(fa!=fb)unionn(fa,fb);
cnt++;
}
for(int j=1;j<=n;++j)
if(fat[j]==j)sum+=(LL)num[j]*(LL)(num[j]-1);//這個組合公式挺好用的
que[i].res=sum;
}
sort(que+1,que+1+q,cmp2);
for(int i=1;i<=q;++i)printf("%lld\n",que[i].res);
}
return 0;
}
The end;