[洛谷P4178] Tree

这道题是比较裸的点分治。

洛谷传送门

poj1741 传送门

点分治是树分治的一种,据说是最常用的。

除了点分治,还有边分治、链分治等等。

点分治的话,每次找到一个点作为根,把树拆成几个部分。

先统计与根有关的答案,再在几个子树内继续拆分下去。

显然那个点最好选树的重心。

具体到这道题,每次选完根以后,先算一遍距离。

如果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。

所以我盯着,那个算法实现完全正确的程序,“查错”了将近一个小时......

原文地址:https://www.cnblogs.com/cervusy/p/9996123.html