https://codeforces.com/problemset/problem/1213/G
题意翻译
题目描述
\mathsf E \color{red}\mathsf{ntropyIncreaser}EntropyIncreaser 有一棵 nn 个点的树,每条边都带权。
她会问你 mm 个问题,每次给你一个正整数 qq,求最大权值不大于 qq 的简单路径数量。
需要注意的是,对于一个点对 (u,v)(u,v) 只记一次,单独一个点不算路径。
输入格式
第一行两个正整数 n,mn,m,意义如题目描述。
接下来 n-1n−1 行,每行三个正整数 u,v,wu,v,w,表示 u,vu,v 之间有一条权为 ww 的无向边。
最后一行 mm 个正整数,表示询问。
输出格式
对于每个询问,输出一行一个整数表示答案。
数据范围
1\le n,m \le 2\times10^51≤n,m≤2×105
1\le u,v \le n1≤u,v≤n
1\le w,q \le 2\times 10^51≤w,q≤2×105
输入输出样例
输入 #1复制
7 5 1 2 1 3 2 3 2 4 1 4 5 2 5 7 4 3 6 2 5 2 3 4 1
输出 #1复制
21 7 15 21 3
输入 #2复制
1 2 1 2
输出 #2复制
0 0
输入 #3复制
3 3 1 2 1 2 3 2 1 3 2
输出 #3复制
1 3 3
说明/提示
The picture shows the tree from the first example:
开始觉得不好处理,感觉是个有点组合数学算的。
思路:类似最小生成树维护。离线维护。
从小到大排序边和提问,当提问的值>=当前边权值,就把当前边的两个点连起来。所以会对应两个连通块的总和贡献。
当a和b合并的时候,记录siz[a]为a中连通块点数量,siz[b]为b中连通块点数量,那么点对数为siz[a]*siz[b],合并完后更新siz,令a为b的父亲节点,此时新的合成的连通块的点数量为siz[a]+=siz[b],siz[b]=0;
启发:询问可以离线维护。
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e5+100;
typedef long long LL;
LL fa[maxn],siz[maxn],c[maxn],cnt;
struct edge{
LL x,y,w;
}edges[maxn];
struct que{
LL p,id;
}ques[maxn];
bool cmp1(edge A,edge B){return A.w<B.w;}
bool cmp2(que A,que B){return A.p<B.p;}
LL find(LL x)
{
if(x==fa[x]) return fa[x];
return fa[x]=find(fa[x]);
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
LL n,m;cin>>n>>m;
for(LL i=1;i<=n;i++) fa[i]=i,siz[i]=1;
for(LL i=1;i<n;i++) cin>>edges[i].x>>edges[i].y>>edges[i].w;
for(LL i=1;i<=m;i++) cin>>ques[i].p,ques[i].id=i;
sort(edges+1,edges+1+n,cmp1);
sort(ques+1,ques+1+m,cmp2);
LL ans=0;LL last=1;
for(LL i=1;i<=m;i++){
for(LL j=last;j<=n;j++){
if(edges[j].w<=ques[i].p)
{
LL x=find(edges[j].x);LL y=find(edges[j].y);
if(x==y) continue;
ans+=siz[x]*siz[y];
siz[x]+=siz[y];siz[y]=0;
fa[y]=x;
last++;
}
else{
break;
}
}
c[ques[i].id]=ans;
}
for(LL i=1;i<=m;i++){
cout<<c[i]<<" ";
}
cout<<endl;
return 0;
}