题目大意:给出一棵有n个节点的树和树中每条边的权值,再给出一个数k,求出树中所有长度小于等于k的路径条数。
点分治模板题,主要思路是:
1.找到树的重心root,将其当作根。
2.对于以root为根的树,一共有两种路径:一种路径过root,一种不过root,即全部在root的一个子树里面。不过由于我们在后面会将树“分治”,所以第二种情况在以后被分治的树中也会变成第一种情况,所以后面只讨论第一种情况。
3.从root开始往子树dfs,用dis[i]表示i节点到root的长度。然后从小到大对dis排序,利用双指针便可以计算出以root为根的子树的所有dis[i]+dis[j]<=k的所有路径条数。
4.不过仔细想想这个做法其实有问题,思考如下的状况:
假如dis[3]+dis[4]>k,那么这一对便不会其中,但是实际上3号点和4号点的距离应该是dis[3]+dis[4]-dis[2],因此上面的算法会漏掉一些可能的解。
这种情况即为上面说的“第二种情况”,由于我们进行分治的时候会讨论到后面的子树,比如我们讨论到了以2为根节点的子树,那么3号节点和4号节点的问题就从“第二种情况”变为“第一种情况”。因此我们只需要把这种情况直接减去即可
5.返回到1,进行分治。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#define LL long long
using namespace std;
const int maxn=1e4+5;
const int inf=0x3f3f3f3f;
int dis[maxn],n,k,maxson[maxn],root,minn,cntson[maxn],s,tot;
LL ans;
bool vis[maxn];
struct Edge{
int u,v,d;
Edge(int u,int v,int d):u(u),v(v),d(d){}
};
vector<int>G[maxn];
vector<Edge>edges;
void add_edge(int u,int v,int d){
edges.push_back(Edge(u,v,d));
G[u].push_back(edges.size()-1);
}
void getroot(int u,int fa){
cntson[u]=1;maxson[u]=0;
for(int i=0;i<G[u].size();i++){
Edge e=edges[G[u][i]];
if(fa==e.v||vis[e.v])continue;
getroot(e.v,u);
cntson[u]+=cntson[e.v];
maxson[u]=max(maxson[u],cntson[e.v]);
}
maxson[u]=max(maxson[u],s-maxson[u]);
if(maxson[u]<minn)root=u,minn=maxson[u];
}
void getdis(int u,int fa,int len){
dis[++tot]=len;
for(int i=0;i<G[u].size();i++){
Edge e=edges[G[u][i]];
if(vis[e.v]||fa==e.v)continue;
getdis(e.v,u,len+e.d);
}
}
int cacul(int u,int del){
tot=0;
memset(dis,0,sizeof(dis));
getdis(u,0,del);
sort(dis+1,dis+tot+1);
int l=1,r=tot,cur=0;
while(l<=r)
if(dis[l]+dis[r]<=k)cur+=r-l,l++;
else r--;//线性计算出满足<=k的路径条数
return cur;
}
void div(int u){
ans+=cacul(u,0);//将所有情况加上
vis[u]=1;
for(int i=0;i<G[u].size();i++){
Edge e=edges[G[u][i]];
if(vis[e.v])continue;
ans-=cacul(e.v,e.d);
//减去所有情况,由于以e.v为根的子树没有e.d这条路,所以在之前附加这个长度
s=cntson[e.v],minn=inf;
getroot(e.v,0);
div(root);
}
}
int main(){
while(scanf("%d%d",&n,&k)){
if(n==0&&k==0)return 0;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)G[i].clear();
for(int i=0;i<edges.size();i++)edges.clear();
int x,y,z;
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
add_edge(x,y,z);
add_edge(y,x,z);
}
s=n;
ans=0,minn=inf;
getroot(1,0);
div(root);
cout<<ans<<endl;
}
}