悬一关,点分治模板题求调。
题意
给定一棵 n 个节点的树,每条边有边权,求出树上两点距离小于等于 k 的点对数量。
(我甘愿为调出代码的dalao做牛做马)
我代码的主要思路也列在下面了,但是最后写挂了。(样例测出来是 6)
/*点分治
这里点切树分三种情况讨论:
1. 点对在此点切出的同一子树内
2. 点对其中之一即改“切点”
3. 点对在此点切出的不同子树内
*/
#include<bits/stdc++.h>
using namespace std;
const int N= 4e4+ 5;
int n, k, p[N], q[N];
bool vis[N];
vector< pair< int, int> > e[N<< 1];
int get_size(int u, int fa)
{
int res= 1;
for(auto v : e[u])
if(!vis[v.second] && v.second!= fa)
res+= get_size(v.second, u);
return res;
}
int get_root(int u, int fa, int ts, int& root)
{
int sum= 1, ms= 0;
for(auto v : e[u])
if(!vis[v.second] && v.second!= fa)
{
int t= get_root(v.second, u, ts, root);
ms= max(ms, t);
sum+= t;
}
ms= max(ts- sum, ms);
if(ms<= (ts>> 1)) root= u;
return sum;
}
void get_dis(int u, int fa, int dis, int& qt)
{
q[++qt]= dis;
for(auto v : e[u])
if(!vis[v.second] && v.second!= fa)
get_dis(v.second, u, dis+ v.first, qt);
}
int get(int a[], int len)
{
int res= 0;
sort(a+ 1, a+ len+ 1);
for(int i= len, j= 0; i> 0; i--)
{
while(j< i && a[j]+ a[i]<= k) j++;
j= min(i- 1, j);
res+= j;
}
return res;
}
int calc(int u)
{
int res= 0;
get_root(u, 0, get_size(u, 0), u);
vis[u]= 1;
//第三种情况
int pt= 0;
for(auto v : e[u])
if(!vis[v.second])
{
int qt= 0;
get_dis(v.second, 0, v.first, qt);
res-= get(q, qt);
for(int i= 1; i<= qt; i++)
{
if(q[i]<= k) res++; //第二种情况
p[++pt]= q[i];
}
}
res+= get(p, pt);
for(auto v : e[u]) if(!vis[v.second]) res+= calc(v.second); //第一种情况
/*因为过来的父节点一定已被 vis 标记,所以只判 vis,就可以不走回去*/
return res;
}
signed main()
{
scanf("%d", &n);
int u, v, w;
for(int i= 1; i< n; i++)
{
scanf("%d%d%d", &u, &v, &w);
e[u].push_back(make_pair(w, v)), e[v].push_back(make_pair(w, u));
}
scanf("%d", &k);
printf("%d", calc(1));
return 0;
}