题目:https://vjudge.net/contest/323699#problem/A
题意:给你一棵树,然后有m个查询,每次查询问一条路径最大边小于给定查询的数量
思路:首先我们看到,我们其实可以计算出每个边权小于查询的所有连通块,然后sum+C(n,2),对每个连通块都加上值,然后就是答案了,但是这里注意查询数很多,我们肯定不能O(n)遍历每个查询,但是思路肯定是计算联通块里组合数的数量,怎么处理呢,我们注意到,他这个边权是的值的大小和我的连通块的有关,我们是否可以利用之前求出来的值呢,答案是可以的,我们可以对查询排序,先求值小的,然后到后面查询的时候用之前的连通块继续延伸,总的所有查询的复杂度我们也只是遍历了一遍树,但是我们dfs不好去处理,这里我们怎么弄呢,连通块的算法就那么几个,dfs,并查集,tarjan,我们可以排除dfs和tarjan,那么我们就肯定是用并查集了,我们对边权也进行排序,然后我们就可以利用每个边进行联通块的合并,然后贡献怎么计算呢,我们会发现A连通块和B连通块,然后A连通块的路径对已经算完了,B连通块的路径对算完了,然后现在要求的一个点在A连通块,一个点在Bl连通块,贡献就加上A.size*B.size
#include<bits/stdc++.h> #define maxn 200005 #define mod 1000000007 using namespace std; typedef long long ll; struct sss{ ll x,y,z; }a[maxn]; struct eee{ ll id,value; }cx[maxn]; ll n,m,q; long long da[maxn]; ll siz[maxn]; ll f[maxn]; int cmp(struct sss x,struct sss y){ return x.z<y.z; } int cmp1(struct eee x,struct eee y){ return x.value<y.value; } int find(int x){ if(x==f[x]) return x; else return f[x]=find(f[x]); } int main(){ scanf("%lld%lld",&n,&m); for(int i=0;i<n-1;i++){ scanf("%lld%lld%lld",&a[i].x,&a[i].y,&a[i].z); } sort(a,a+n-1,cmp); for(int i=0;i<m;i++){ cx[i].id=i; scanf("%lld",&cx[i].value); } sort(cx,cx+m,cmp1); for(int i=1;i<=n;i++){ f[i]=i; siz[i]=1; } /*for(int i=0;i<n-1;i++){ printf("%d %d %d ",a[i].x,a[i].y,a[i].z); }*/ ll dex=0; ll sum=0; for(int i=0;i<n-1;i++){ if(a[i].z<=cx[dex].value){ int xx=find(a[i].x); int yy=find(a[i].y); if(xx!=yy){ sum+=siz[xx]*siz[yy]; f[yy]=xx; siz[xx]+=siz[yy]; } } else{ da[cx[dex].id]=sum; dex++; if(dex==m) break; i--; } } for(;dex<m;dex++){ da[cx[dex].id]=sum; } for(int i=0;i<m;i++){ printf("%lld ",da[i]); } }