这道题是比较裸的点分治。
点分治是树分治的一种,据说是最常用的。
除了点分治,还有边分治、链分治等等。
点分治的话,每次找到一个点作为根,把树拆成几个部分。
先统计与根有关的答案,再在几个子树内继续拆分下去。
显然那个点最好选树的重心。
具体到这道题,每次选完根以后,先算一遍距离。
如果n^2暴力统计距离,太慢了。
我们排个序之后,双指针扫一遍。
如果现在dis[l]+dis[r]大于k,那么实际上r就没有用了,因为l++之后的dis[l]肯定比现在的dis[l]大。
所以某个r没有用了,我们就可以r--了。
每一次统计的答案不仅仅是跨越根的路径(如图):
也包含了不跨根的非法路径(如图):
所以我们再在每个子树中分别统计一遍,减掉就行了。
然后再递归进行下一层的分治。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 typedef long long ll; 7 8 int n,k,ans; 9 int hd[40005],to[80005],nx[80005],len[80005],ec; 10 11 void edge(int af,int at,int el) 12 { 13 to[++ec]=at; 14 len[ec]=el; 15 nx[ec]=hd[af]; 16 hd[af]=ec; 17 } 18 19 int rt,tp,tot; 20 ll buf[40005],dis[40005]; 21 int sz[40005],mx[40005]; 22 int del[40005]; 23 24 void weigh(int p,int fa) 25 { 26 sz[p]=1;mx[p]=0; 27 for(int i=hd[p];i;i=nx[i]) 28 { 29 int tar=to[i]; 30 if(tar==fa||del[tar])continue; 31 weigh(tar,p); 32 sz[p]+=sz[tar]; 33 mx[p]=max(mx[p],sz[tar]); 34 } 35 mx[p]=max(mx[p],tot-sz[p]); 36 if(mx[p]<mx[rt])rt=p; 37 } 38 39 void dfs(int p,int fa) 40 { 41 buf[++tp]=dis[p]; 42 for(int i=hd[p];i;i=nx[i]) 43 { 44 int tar=to[i]; 45 if(tar==fa||del[tar])continue; 46 dis[tar]=dis[p]+len[i]; 47 dfs(tar,p); 48 } 49 } 50 51 int count(int p,int d0) 52 { 53 dis[p]=d0;tp=0; 54 dfs(p,0); 55 sort(buf+1,buf+tp+1); 56 int l=1,r=tp,ret=0; 57 while(l<r) 58 { 59 if(buf[l]+buf[r]>k)r--; 60 else ret+=r-(l++); 61 } 62 return ret; 63 } 64 65 void conquer(int p) 66 { 67 ans+=count(p,0); 68 del[p]=1; 69 for(int i=hd[p];i;i=nx[i]) 70 { 71 int tar=to[i]; 72 if(del[tar])continue; 73 ans-=count(tar,len[i]); 74 tot=sz[tar];rt=0; 75 weigh(tar,0); 76 conquer(rt); 77 } 78 } 79 80 int main() 81 { 82 scanf("%d",&n); 83 for(int i=1;i<n;i++) 84 { 85 int ff,tt,vv; 86 scanf("%d%d%d",&ff,&tt,&vv); 87 edge(ff,tt,vv); 88 edge(tt,ff,vv); 89 } 90 scanf("%d",&k); 91 tot=mx[0]=n; 92 weigh(1,0); 93 conquer(rt); 94 printf("%d",ans); 95 return 0; 96 }
连了双向边但是没有开双倍数组导致凉凉......
但是洛谷的评测姬告诉我是MLE而不是RE。
所以我盯着,那个算法实现完全正确的程序,“查错”了将近一个小时......