【知识点】点分治

  • 点分治简介

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

 

  • 步骤:
  1. 找到当前树的重心,以重心为根节点
  2. 处理经过当前根节点的路径
  3. 删除根节点
  4. 对于生成的每棵子树,重复以上步骤

 

找重心:O(n)

void findrt(int u,int fa){
    //sz表示子树的大小,son表示点的最大子树的大小 
    sz[u]=1,son[u]=0;
    for(int i=head[u];i;i=Next[i]){
        int v=ver[i];
        if(vis[v]||v==fa) continue;
        findrt(v,u);
        sz[u]+=sz[v];
        son[u]=max(son[u],sz[v]);
    }
    //size表示整棵树的大小 
    //因为这是一棵无根树,所以包括它的父亲在内的那一片也应该算作它的子树 
    son[u]=max(son[u],size-sz[u]);
    if(son[u]<mx) mx=son[u],rt=u;//最大子树大小最小
}

 

 

分治相关:

void divide(int u){
    ans+=solve(u,0);//把当前节点的答案加上去 
    vis[u]=1;//把节点标记,防止陷入死循环 
    for(int i=head[u];i;i=Next[i]){
        //分别处理每一棵子树 
        int v=ver[i];
        if(vis[v]) continue;
        ans-=solve(v,edge[i]);//容斥原理,下面说 
     mx=inf,rt=0;
        size=sz[v]>sz[u]?totsz-sz[u]:sz[v];//这里应该这样写才是对的
        //把所有信息更新,递归进子树找重心,并继续分治 
        findrt(v,0);
        divide(rt);
    }
}

 

好啦,贴了一些自己独立敲不出来的代码,试图完全的理解,失败了。。。

所以就直接上板子!!!

板子也找了半天!对于我这种没有完全理解透的,只能尝试找一个可以自己改成适用各种题目的板子:

 

  • 这个是一个大神用树状数组桶排序做的:(有一说一,我应该不咋会用,我可能就是放这里%%)

题目链接:https://www.luogu.com.cn/problem/P4178

#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);
    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); 
        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){
        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;
}

 

题目链接:https://www.luogu.com.cn/problemnew/solution/P3806

#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int inf=10000000;
const int maxn=100010;
int n,m;
struct node{int v,dis,nxt;}E[maxn<<1];
int tot,head[maxn];
int maxp[maxn],size[maxn],dis[maxn],rem[maxn];
int vis[maxn],test[inf],judge[inf],q[maxn];
int query[1010];
int sum,rt;
int ans;

void add(int u,int v,int dis)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    E[tot].dis=dis;
    head[u]=tot;
}

void getrt(int u,int pa)
{
    size[u]=1; maxp[u]=0;
    for(int i=head[u];i;i=E[i].nxt) 
    {
        int v=E[i].v;
        if(v==pa||vis[v]) continue;
        getrt(v,u);
        size[u]+=size[v];
        maxp[u]=max(maxp[u],size[v]);
    }
    maxp[u]=max(maxp[u],sum-size[u]);
    if(maxp[u]<maxp[rt]) rt=u;
}

void getdis(int u,int fa)
{
    rem[++rem[0]]=dis[u];
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==fa||vis[v])continue;
        dis[v]=dis[u]+E[i].dis;
        getdis(v,u);
    }
}

void calc(int u)
{
    int p=0;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(vis[v])continue;
        rem[0]=0; dis[v]=E[i].dis;
        getdis(v,u);//处理u的每个子树的dis

        for(int j=rem[0];j;--j)//遍历当前子树的dis
        for(int k=1;k<=m;++k)//遍历每个询问
        if(query[k]>=rem[j])
        test[k]|=judge[query[k]-rem[j]];
        //如果query[k]-rem[j]d的路径存在就标记第k个询问

        for(int j=rem[0];j;--j)//保存出现过的dis于judge
        q[++p]=rem[j],judge[rem[j]]=1;
    }
    for(int i=1;i<=p;++i)//处理完这个子树就清空judge
    judge[q[i]]=0;//特别注意一定不要用memset,会T

}

void solve(int u)
{   
    //judge[i]表示到根距离为i的路径是否存在
    vis[u]=judge[0]=1; calc(u);//处理以u为根的子树
    for(int i=head[u];i;i=E[i].nxt)//对每个子树进行分治
    {
        int v=E[i].v;
        if(vis[v])continue;
        sum=size[v]; maxp[rt=0]=inf;
        getrt(v,0); solve(rt);//在子树中找重心并递归处理
    }
}

int main()
{
    n=read();m=read();
    for(int i=1;i<n;++i)
    {
        int u=read(),v=read(),dis=read();
        add(u,v,dis);add(v,u,dis);
    }
    for(int i=1;i<=m;++i)
    query[i]=read();//先记录每个询问以离线处理

    maxp[rt]=sum=n;//第一次先找整棵树的重心
    getrt(1,0); 
    solve(rt);//对树进行点分治

    for(int i=1;i<=m;++i)
    {
        if(test[i]) printf("AYE\n");
        else printf("NAY\n");
    }
    return 0;
}

 

上面这种是容斥思想加两指针扫;

有一些如果不是问方案数而是最值问题就不适合

 

未写完待续...

posted @ 2020-01-08 00:49  carrotmvp  阅读(179)  评论(0编辑  收藏  举报