点分治模板 and Tree POJ-1741 题解
点分治模板 and Tree POJ-1741 题解
感觉这道题挺适合点分治入门的。
Part 0
点分治可以解决很多询问路径长度之类的问题。
一般的路径长度问题(就如本题),假如数据范围小,就可以先预处理出每个点到根的距离,再枚举路径的两端点,求出LCA,就可以算出每个点对的距离。
但这要的复杂度啊,还是太大了,于是,就有了点分治。
Part 1
如果对于一棵树,我们确定了一个根,那么对于任意一条路径,它只有两种可能,要么经过根,要么不经过根,也就是在根节点的某个子节点的子树内,故我们处理完经过根的路径,再把根从这颗树上删掉(也就是把一棵树分成了好多颗树)。然后再处理其他路径。其他路径就不会经过被删去的点,这样复杂度就会降下来。
如何处理跟放在后面再讲,因为与点分治模板无关。
首先,如何确定点分治的每一个根是有套路的,如果乱确定复杂度是很大的,我们要使断开后每个子树尽量平均(也就是断开后的最大的子树的大小最小)。于是便有了如下确定根的方法。
void Get_Root(int now,int fa){//sz 子树大小 mx_sz 子节点对应的最大子树大小 mark 这个点是否已被删除
sz[now]=1,mx_sz[now]=0;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(mark[nxt]||nxt==fa)continue;
Get_Root(nxt,now);
sz[now]+=sz[nxt];
check_max(mx_sz[now],sz[nxt]);
}
check_max(mx_sz[now],tot_sz-sz[now]);//要注意还要处理上面的子树。
if(mx_sz[now]<mx_sz[Root]||Root==0)Root=now;
}
//调用就是这样
Root=0,tot_sz=n;
Get_Root(1,0);
dfs(Root);
其实这也是点分治的精髓,也是解决这类问题复杂度的保证。
Part 2
以下就是解决本题的方法(其中也包含点分治模板)
处理经过根的路径可以用DP来处理。因为一条路径由根可以分成两条路径,或是一条路径(另一条就是长度为零的路径)。我们可以枚举一条路径,从而得出另一条路径的长度区间,从而计数,当然,我们要先把所有一端为根的路径预处理出来。如下:
void Get_dis(int now,int fa,int res){
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(nxt==fa||mark[nxt])continue;
dis[++n_dis]=res+edge[i].d;
Get_dis(nxt,now,dis[n_dis]);
}
}
但是还有一种情况没有考虑到,那就是如果两条路径属于同一个根的子节点的子树,也就是他们的关系如下。这样可以先处理一遍之后再在此颗子树里单独处理一遍这种路径,就可以删去这种不合法的状态。
然后就可以快乐的计数了。
int Get_ans(int now,int add){
dis[n_dis=1]=add;//若是初次计数,这个add是用来表示那种另一个长度为0的路径的。
//但若是为了消去不合法情况的,这个add就是用来算上那个子节点到当前枚举这个根的距离的
Get_dis(now,0,add);//注意初始为add
sort(dis+1,dis+n_dis+1);//把所有距离排序
int L=1,res=0;
while(L<n_dis&&K-dis[L]>=dis[L]){
int R=Find_R(L+1,K-dis[L]);//找到最长的可以匹配的路径
if(L<=R)res+=R-L;
L++;
}
return res;
}
void Get_Root(int now,int fa){//sz 子树大小 mx_sz 子节点对应的最大子树大小 mark 这个点是否已被删除
sz[now]=1,mx_sz[now]=0;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(mark[nxt]||nxt==fa)continue;
Get_Root(nxt,now);
sz[now]+=sz[nxt];
check_max(mx_sz[now],sz[nxt]);
}
check_max(mx_sz[now],tot_sz-sz[now]);//要注意还要处理上面的子树。
if(mx_sz[now]<mx_sz[Root]||Root==0)Root=now;
}
void dfs(int now){
mark[now]=1,ans+=Get_ans(now,0);
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(mark[nxt])continue;//如果这个点已经删去
ans-=Get_ans(nxt,edge[i].d);//删去不合法计数,记住要算上它到当前枚举的根的距离
Root=0,tot_sz=sz[nxt];
Get_Root(nxt,now),dfs(Root);
}
}
整个代码就是酱紫
#include<cstdio>
#include<cstring>
#include<algorithm>
#define M 10005
using namespace std;
void check_max(int &x,int y){if(x<y)x=y;}
struct E{
int to,nx,d;
}edge[M<<1];
int tot,head[M];
void Addedge(int a,int b,int d){
edge[++tot].to=b;
edge[tot].nx=head[a];
edge[tot].d=d;
head[a]=tot;
}
bool mark[M];
int sz[M],mx_sz[M],tot_sz;
int Root,ans;
int dis[M],n_dis,K;
void Get_dis(int now,int fa,int res){
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(nxt==fa||mark[nxt])continue;
dis[++n_dis]=res+edge[i].d;
Get_dis(nxt,now,dis[n_dis]);
}
}
int Find_R(int L,int x){
int R=n_dis,res=0;
while(L<=R){
int mid=(L+R)>>1;
if(dis[mid]<=x){
L=mid+1;
res=mid;
}else R=mid-1;
}
return res;
}
int Get_ans(int now,int add){
dis[n_dis=1]=add;//若是初次计数,这个add是用来表示那种另一个长度为0的路径的。
//但若是为了消去不合法情况的,这个add就是用来算上那个子节点到当前枚举这个根的距离的
Get_dis(now,0,add);//注意初始为add
sort(dis+1,dis+n_dis+1);//把所有距离排序
int L=1,res=0;
while(L<n_dis&&K-dis[L]>=dis[L]){
int R=Find_R(L+1,K-dis[L]);//找到最长的可以匹配的路径
if(L<=R)res+=R-L;
L++;
}
return res;
}
void Get_Root(int now,int fa){//sz 子树大小 mx_sz 子节点对应的最大子树大小 mark 这个点是否已被删除
sz[now]=1,mx_sz[now]=0;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(mark[nxt]||nxt==fa)continue;
Get_Root(nxt,now);
sz[now]+=sz[nxt];
check_max(mx_sz[now],sz[nxt]);
}
check_max(mx_sz[now],tot_sz-sz[now]);//要注意还要处理上面的子树。
if(mx_sz[now]<mx_sz[Root]||Root==0)Root=now;
}
void dfs(int now){
mark[now]=1,ans+=Get_ans(now,0);
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(mark[nxt])continue;//如果这个点已经删去
ans-=Get_ans(nxt,edge[i].d);//删去不合法计数,记住要算上它到当前枚举的根的距离
Root=0,tot_sz=sz[nxt];
Get_Root(nxt,now),dfs(Root);
}
}
int main(){
int n;
while(scanf("%d%d",&n,&K)){
if(!n&&!K)return 0;
tot=0;
ans=0;
memset(head,0,sizeof(head));
memset(mark,0,sizeof(mark));//注意每次要清空哦
for(int i=1;i<n;i++){
int a,b,d;
scanf("%d%d%d",&a,&b,&d);
Addedge(a,b,d);
Addedge(b,a,d);
}
Root=0,tot_sz=n;
Get_Root(1,0);
dfs(Root);
printf("%d\n",ans);
}
return 0;
}
当然,这个模板不能适应所有点分治的题目,但大体上还是一样的,每个题目所求不同,要随机应变。