点分治模板 入门 学习

参考视频:视频

参考博客:博客

点分治是树分治的一种,是处理大规模树上路径的问题的强有力武器

另一种树分治是边分治,稍麻烦些,几乎所有的点分治都可以边分治

一颗树上的路径分为两种:   经过根节点  和    不经过根节点的

若我们把根节点删去,则可以生成若干棵以原根节点的儿子为根节点的子树

子树又分为:经过根节点  和    不经过根节点的

点分治算法步骤:

1、处理当前经过根节点的路径

2、删除根节点

3、生成每棵子树重复1、 2、步骤

说到这 大概就知道为什么每次子树要找重心了吧

至于怎么找重心,自行百度,这里不再赘述

至于怎么统计合并子树  并统计答案,一般还要套一个其他的算法,比如树状数组,或者线段树,还要带可撤销的(因为有删除根点操作)

来看一道题,顺便贴贴基本模板

P4178

给定一棵 n 个节点的树,每条边有边权,求出树上两点距离小于等于 k 的点对数量。

include<iostream>
#include<cstring>
#include<cstdio>

using namespace std;

inline int rd(){
    int ret=0,f=1;char c;
    while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
    while(isdigit(c))ret=ret*10+c-'0',c=getchar();
    return ret*f;
}

const int MAXN=262144;
const int INF=1<<29;

int n,m;

struct Edge{
    int next,to,w;
}e[MAXN<<1];
int ecnt,head[MAXN];
inline void add(int x,int y,int w){
    e[++ecnt].next = head[x];
    e[ecnt].to = y;
    e[ecnt].w = w;
    head[x] = ecnt;
}

int t[MAXN];
void update(int x,int val){
    for(int i=x;i<=m;i+=i&-i)t[i]+=val;
}
int query(int x){
    if(x==0) return 0;
    int ret=0;
    for(int i=x;i;i-=i&-i)ret+=t[i];
    return ret;
}

bool vis[MAXN];

int siz[MAXN];
void getsiz(int x,int pre){//预处理树上  每个节点的子树大小 
    siz[x]=1;
    for(int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if(vis[v]||v==pre) continue;
        getsiz(v,x);
        siz[x]+=siz[v];
    }
}
int mn=INF,root;
void getroot(int x,int pre,int tot){
    int mx=0;
    for(int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if(vis[v]||v==pre) continue;
        mx=max(mx,siz[v]);
        getroot(v,x,tot);
    }
    mx=max(mx,tot-siz[x]);
    if(mx<mn) mn=mx,root=x;
}
int s[MAXN],sav[MAXN];

void dfs(int x,int pre,int dis){
    if(dis>m) return;
    s[++s[0]]=dis;
	sav[++sav[0]]=dis;
	
    for(int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if(vis[v]||v==pre) continue;
        dfs(v,x,dis+e[i].w);
    }
}

int ans;

void dac(int x){//Divide and Conquer :)
    sav[0]=0;mn=n;
    
    
    getsiz(x,-1);//以x为根节点  重新getsize  一下  
    getroot(x,-1,siz[x]);//先找到一个重心 
    
    
    
    int u=root;vis[u]=1;///删掉根节点 
    for(int i=head[u];i;i=e[i].next){//维护答案 
        int v=e[i].to;
        if(vis[v]) continue;
        s[0]=0;
		dfs(v,u,e[i].w); //以u节点为根的   经过根节点的路径保存至s 和  sav里面 
        for(int j=s[0];j>=1;j--){
            if(s[j]>m) continue;
            ans+=query(m-s[j]);//权值树状数组 
        }
        
        
        for(int j=s[0];j>=1;j--){
            if(s[j]>m) continue;
            update(s[j],1);//更新答案 
            ans++;
        }
    }
    
    
    
    for(int i=sav[0];i>=1;i--){//删掉答案 
        if(sav[i]>m) continue;
        update(sav[i],-1);
    }
    
    
    for(int i=head[u];i;i=e[i].next){//去掉根u  继续点分治 
        int v=e[i].to;
        if(vis[v]) continue;
        dac(v);
    }
}



int main(){
    n=rd();
    int x,y,w;
    for(int i=1;i<n;i++){
        x=rd();y=rd();w=rd();
        add(x,y,w);add(y,x,w);
    }
    m=rd();
    
    dac(1);
    cout<<ans;
    return 0;
}
发布了519 篇原创文章 · 获赞 69 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_41286356/article/details/105469973