在B站看了点分治后,照着敲了模板题
https://www.bilibili.com/video/av53560289?from=search&seid=8511450481402711616
点分治感觉根之前学的CDQ,启发式分治,等很像,都是先分再治。
分的复杂度尽量logn,治的复杂度线性或带上logn。
即:递归logn层,每层的复杂度在可控范围内。
树上点分治:顾名思义,即按照点进行分治,每次删除一个点,对每个子树再进行一次处理。
如果删除的是重心,则最多删logn次,可以保证无大小大于1的子树。
这一题是求树上任意2点距离小于k的个数。
考虑根rt的树,2点距离要么是同一子树,要么是不同子树。
我们分治后,对每层的每个子树:先求出子树所有点到子树根的距离。
由于区分是否是同一子树比较困难,干脆我们就不区分,加多的后面再减去。
得到num个距离后,用排序+尺取Onlogn的求出任意2点距离小于k的个数。
这样会多算:同一子树2点间距离小于k,(因为每层递归都会算一遍,我们算不同子树刚好不重不漏)
所以后面求子树距离时减去任意2点距离和+子树根到父亲的距离小于k的个数(这个个数就是刚才父亲计算距离时多算的,可以再纸上画下就明白了,任意点到子树根的距离+子树到父亲的距离其实就是:这棵子树任意点到父亲的距离)
分治下去就算完了。
代码:
//点分治,分==找重心,治==尺取+排序 复杂度n*(logn)^2
//#include <bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int M = 1e4+7;
struct node
{
int to,v,nxt;
}E[M*2];
int head[M*2];
int cnt;
void add(int x,int y,int v)//x-y有一条权值v的路径
{
E[++cnt].to=y;
E[cnt].nxt=head[x];
E[cnt].v=v;
head[x]=cnt;
}
int rs,rt;//rt即重心 ,rs为重儿子大小
int vis[M];//是否删除该点
int size[M];
int dis[M];//当前子树每个点到子树根的距离
int all;//当前子树大小
ll ans;//最终答案
int num;//子树每个点的id
int n,k;
void getroot(int x,int fa)//查找重心
{
// printf("getroot: %d %d\n",x,fa);
size[x]=1;//子树x大小
int mx=0;// 删掉x后分成最大子树大小
for(int i=head[x];i!=0;i=E[i].nxt)
{
int y=E[i].to;
if(y==fa||vis[y])continue;//不能访问父亲,和不是该分治后的树的点
getroot(y,x);
size[x]+=size[y];//从子节点向父亲结点递推
mx=max(mx,size[y]);//x的每个儿子的大小
}
mx=max(mx,all-size[x]);//除了x和他的儿子的数量,即x的另一颗子树
if(mx<rs) rt=x,rs=mx;//更新重心
}
void getdis(int u,int fa,int dist)//得到当前子树里每个点到子树根的距离
{
dis[++num]=dist;
for(int i=head[u];i!=0;i=E[i].nxt)
{
int v=E[i].to;
if(v==fa||vis[v])continue;
getdis(v,u,dist+E[i].v);
}
}
void cal(int u,int op,int len)
{
num=0;getdis(u,0,len);//得到dis数组: u子树所有点到u的距离
sort(dis+1,dis+1+num);
int res=0;
int l=1,r=num;
while(l<r)//two-points统计答案
{
while(dis[l]+dis[r]>k&&l<r)r--;
res+=r-l;
l++;
}
ans+=op*res;
}
void dfs(int u)//点分治
{
vis[u]=1;//删除了u这个点 分治出子树
cal(u,1,0);//计算包含根的答案
for(int i=head[u];i!=0;i=E[i].nxt)
{
int v=E[i].to;
if(vis[v])continue;
cal(v,-1,E[i].v);//减去同一个子树合并的不合法答案
rs=all=size[v];rt=0;
getroot(v,0);
dfs(rt);//递归统计子树的答案
}
}
int main()
{
while(~scanf("%d%d",&n,&k)&&n)
{
int x,y,v;
memset(head,0,sizeof(head));
memset(vis,0,sizeof(vis));
num=cnt=ans=0;
rs=all=n;//初始化
for(int i=1;i<=n-1;i++)
scanf("%d%d%d",&x,&y,&v),add(x,y,v),add(y,x,v);
getroot(1,0);
dfs(rt);
printf("%lld\n",ans);
}
return 0;
}