题意:
给一棵n个顶点的树,m个询问,
每个询问给k,图中有多少对点对u,v(u<v)
满足u,v路径上的最长边不大于k
分析:
结构体存储所有边和所有询问
对边按权值从小到大排序,对询问按k从小到大排序
遍历所有询问
对于每个询问中的k,将所有边权小于等于k的边的端点用并查集合并成同一连通块
此时每个 连通块中任意点对间的最长边都小于等于k,
每次合并两个连通块,假设两个连通块中的点数为x和y,则点对数量增加xy,答案累加xy
(x所在连通块的每个点 连上了y中的每个点,总增加路径为xy)
也可以写成
答案减去x(x-1)/2减去y(y-1)/2加上(x+y)(x+y-1)/2;
减去旧的连通块中的总路径数量,计算新的连通块中的总路径数量
ps:
居然原题。
hdu3938,hdu5441
code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
#define int long long//necessary
const int maxm=2e5+5;
struct Edge{
int a,b,c;
bool operator<(Edge &a){
return c<a.c;
}
}e[maxm];
struct Query{
int k,id;
bool operator<(Query &a){
return k<a.k;
}
}q[maxm];
int pre[maxm];
int num[maxm];
int res[maxm];
void init(){
for(int i=0;i<maxm;i++){
pre[i]=i;
num[i]=1;
}
}
int ffind(int x){
return pre[x]==x?x:pre[x]=ffind(pre[x]);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
init();
int n,m;
cin>>n>>m;
for(int i=0;i<n-1;i++){
cin>>e[i].a>>e[i].b>>e[i].c;
}
sort(e,e+n-1);
for(int i=0;i<m;i++){
cin>>q[i].k;
q[i].id=i;
}
sort(q,q+m);
int ans=0;
int now=0;
for(int i=0;i<m;i++){
while(now<n-1&&e[now].c<=q[i].k){
int x=ffind(e[now].a);
int y=ffind(e[now].b);
if(x!=y){
ans+=num[x]*num[y];//路径增加数量(即点对增加数量)
/*
或者写成:
ans-=num[x]*(num[x]-1)/2;//减去旧的连通块中的总路径数量
ans-=num[y]*(num[y]-1)/2;//减去旧的连通块总路径数量
ans+=(num[x]+num[y])*(num[x]+num[y]-1)/2;//计算新的连通块中的总路径数量
*/
pre[y]=x;//合并
num[x]+=num[y];//合并
}
now++;
}
res[q[i].id]=ans;
}
for(int i=0;i<m;i++){
cout<<res[i]<<' ';
}
return 0;
}