题目描述:
给出一棵树,求距离不超过k的点对对数。
题解:
点分治板子题。
对于一棵树,我们可以$O(n)$时间求出其重心。(重心:切开这个点得到一堆树,所有树的最大大小最小)
如果我们将一棵树不断找重心->分开->找重心……,我们可以将这棵树分成若干部分。
这个东西叫点分治。
因此将一棵树不断分开后,每个点都会作一次中心。
如果我们将第一层找到的重心称为一级重心,第二层找到的重心称为二级重心……的话,
对于任意一条树链,经过的最高级重心只能有一个,而且从这个重心搜索一定可以搜到这条树链。
这就是点分治能处理树链问题的原因。
对于本题,我们可以求出以x为重心的树内距离x为i的有多少个点。
然后对于和$<=k$的更新答案。
但是还有一种情况,就是路径a为x->y->……,路径b为x->y->……,而a+b可以更新答案。
这样我们的答案其实是偏大的。
因此我们可以搞一下容斥,将y相同的直接搞掉,这样的话得出的就是答案了。
代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define N 10050 inline int rd() { int f=1,c=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){c=10*c+ch-'0';ch=getchar();} return f*c; } int n,k,hed[N],cnt; struct EG { int to,nxt,v; }e[2*N]; void ae(int f,int t,int v) { e[++cnt].to = t; e[cnt].nxt = hed[f]; e[cnt].v = v; hed[f] = cnt; } int rt,sum,mx[N],mrk[N],siz[N],ans; void get_rt(int u,int fa) { mx[u] = 0;siz[u] = 1; for(int j=hed[u];j;j=e[j].nxt) { int to = e[j].to; if(to==fa||mrk[to])continue; get_rt(to,u); siz[u]+=siz[to]; if(siz[to]>mx[u])mx[u]=siz[to]; } mx[u]=max(mx[u],sum-siz[u]); if(mx[u]<mx[rt])rt=u; } int st[N],tl; void dfs(int u,int fa,int dep) { st[++tl] = dep; for(int j=hed[u];j;j=e[j].nxt) { int to = e[j].to; if(to==fa||mrk[to])continue; dfs(to,u,dep+e[j].v); } } int sol(int u,int fa,int dep) { tl=0; dfs(u,fa,dep); sort(st+1,st+1+tl); int l = 1,r = tl,ret = 0; while(l<r) { if(st[l]+st[r]<=k) { ret+=r-l; l++; }else r--; } return ret; } void work() { ans+=sol(rt,0,0); mrk[rt]=1; for(int j=hed[rt];j;j=e[j].nxt) { int to = e[j].to; if(mrk[to])continue; ans-=sol(to,rt,e[j].v); } for(int j=hed[rt];j;j=e[j].nxt) { int to = e[j].to; if(mrk[to])continue; rt=0,sum=siz[to]; get_rt(to,0); work(); } } void init() { rt=sum=ans=cnt=0; memset(mrk,0,sizeof(mrk)); memset(hed,0,sizeof(hed)); } int main() { mx[0] = 0x7fffffff; while(1) { n=rd(),k=rd(); init(); if(!n&&!k)break; for(int f,t,v,i=1;i<n;i++) { f = rd(),t = rd(),v = rd(); ae(f,t,v),ae(t,f,v); } rt=0;sum=n; get_rt(1,0); work(); printf("%d ",ans); } return 0; }