题意:
一颗有边权的树,问有多少对点距离和小于k
思路:
树分治
我们取一颗树的重心,算出所有点到这个点的距离,然后我们就能通过排序去o(n)地找出有多少对是满足的,但是这样很明显有的对是不正确的,就是这两个点的lca并不是当前重心的时候
我们可以想到,当且仅当两点在同一以重心儿子为根的子树中,才能发生错误,我们就可以单独求一次某个子树的不符合数量即可
然后我们删去当前重心,继续去看拆掉重心后的子树,然后重复之前的步骤即可
复杂度是
O(nlognlogn)
错误及反思:
t了好多发,主要是没注意到maxn应该是每次dfs的时候都应该重新算,不重新算的话重心是错的
另外树分治的复杂度是可以保证的,每次取重心,所以最多logn次,每次遍历全部点,所以总体复杂度是nlogn
代码:
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 10100;
struct EDGE{
int to,val,next;
}e[N*2];
int first[N],n,k,tot,si[N],maxn[N];
bool did[N];
vector<int> len;
void addedge(int x,int y,int z){
e[tot].to=y;
e[tot].val=z;
e[tot].next=first[x];
first[x]=tot++;
e[tot].to=x;
e[tot].val=z;
e[tot].next=first[y];
first[y]=tot++;
}
void dfs1(int now,int fa){
si[now]=1;
maxn[now]=0;
for(int i=first[now];i!=-1;i=e[i].next)
if(e[i].to!=fa&&!did[e[i].to]){
dfs1(e[i].to,now);
si[now]+=si[e[i].to];
maxn[now]=max(maxn[now],si[e[i].to]);
}
}
void dfs3(int now,int fa,int& root,int& num,int t){
int MA=max(maxn[now],si[t]-si[now]);
if(MA<num){
num=MA;
root=now;
}
for(int i=first[now];i!=-1;i=e[i].next)
if(e[i].to!=fa&&!did[e[i].to])
dfs3(e[i].to,now,root,num,t);
}
void dfs2(int now,int fa,int tlen){
for(int i=first[now];i!=-1;i=e[i].next)
if(e[i].to!=fa&&!did[e[i].to])
dfs2(e[i].to,now,tlen+e[i].val);
len.push_back(tlen);
}
int cal(int v,int d){
len.clear();
dfs2(v,-1,d);
sort(len.begin(),len.end());
int l=0,r=len.size()-1,ans=0;
while(l<r){
if(len[l]+len[r]<=k) ans+=r-l,l++;
else r--;
}
return ans;
}
int solve(int now){
int root,num=1e9;
dfs1(now,-1);
dfs3(now,-1,root,num,now);
int ans=cal(root,0);
did[root]=true;
for(int i=first[root];i!=-1;i=e[i].next)
if(!did[e[i].to]){
ans-=cal(e[i].to,e[i].val);
ans+=solve(e[i].to);
}
return ans;
}
void init(){
tot=0;
memset(first,-1,sizeof(first));
memset(did,false,sizeof(did));
}
int main(){
while(scanf("%d%d",&n,&k)&&n){
init();
for(int i=0,u,v,w;i<n-1;i++){
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
printf("%d\n",solve(1));
}
}