点分治

11 篇文章 1 订阅
2 篇文章 0 订阅

目录

点分治

点分治是树分治中应用最广泛的一种,常用于解决一类求树上满足条件的点对相关的问题,与其它的算法与数据结构结合后能解决多种类型的树上问题,可以说其作用媲美树链剖分。

介绍

树分治的中心思想就是将一个规模很大的问题分解为几个两个相同形式的子问题,然后进行合并。

我们首先面临的问题就是如何将一颗树最优地划分为两块,而本文所涉及的点分治便是划分方法的其中一种;其思想主要是以一个点为基准,然后处理过此点的链或点对,至于其它的不断分治下去处理。

我们称作为分治基准点的这个点为重心,那么我们肯定希望它的子树大小比较平均,也就是说,我们要找到一个最大子树最小的点作为重心,具体做法很简单,注意考虑在深度优先遍历意义下作为点祖先的那些点组成的子树即可。

可以证明:点分治的时间复杂度为 O(log2N) .

int vis[MAXN],rt;
int mx[MAXN],sz[MAXN],min,dis[MAXN];
void calSz(int u,int fa)//计算子树大小
{
    sz[u]=1;mx[u]=0;
    for(int i=G[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa&&!vis[v])
        {
            calSz(v,u);
            sz[u]+=sz[v];
            mx[u]=std::max(mx[u],sz[v]);
        }
    }
}

void calRt(int r,int u,int fa)//r:表示要找以 r 为根的子树的重心
{
    mx[u]=std::max(mx[u],sz[r]-sz[u]);
    if(mx[u]<min) min=mx[u],rt=u;
    for(int i=G[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa&&!vis[v]) calRt(r,v,u);
    }
}

应用

[POJ1741]Tree【男人八题No.5】

给出一颗树,求距离不大于 k 的点对个数

点数 n104

对每一个中心求连线过这个点对距离,排序,随手求一下,加到答案里。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
const int MAXN=1e4+5,MAXM=MAXN<<1;

struct E{int next,to,val;}e[MAXM];int G[MAXN],ecnt;
void addEdge(int u,int v,int w){e[++ecnt]=(E){G[u],v,w};G[u]=ecnt;}

int n,k,vis[MAXN],ans,rt,num;

int mx[MAXN],sz[MAXN],min,dis[MAXN];
void calSz(int u,int fa)
{
    sz[u]=1;mx[u]=0;
    for(int i=G[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa&&!vis[v])
        {
            calSz(v,u);
            sz[u]+=sz[v];
            mx[u]=std::max(mx[u],sz[v]);
        }
    }
}

void calRt(int r,int u,int fa)
{
    mx[u]=std::max(mx[u],sz[r]-sz[u]);
    if(mx[u]<min) min=mx[u],rt=u;
    for(int i=G[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa&&!vis[v])calRt(r,v,u);
    }
}

void calDis(int u,int d,int fa)
{
    dis[++num]=d;
    for(int i=G[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa&&!vis[v]) calDis(v,d+e[i].val,u);
    }
}

int calc(int u,int d)
{
    int res=0;num=0;
    calDis(u,d,0);
    std::sort(dis+1,dis+num+1);
    int i=1,j=num;
    while(i<j)
    {
        while(dis[i]+dis[j]>k&&i<j)j--;
        res+=j-i;i++;
    }
    return res;
}

void dc(int u)
{
    min=n;calSz(u,0);calRt(u,u,0);
    ans+=calc(rt,0);vis[rt]=true;
    for(int i=G[rt];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!vis[v])
        {
            ans-=calc(v,e[i].val);
            dc(v);
        }
    }
}

int main()
{
    while(scanf("%d%d",&n,&k)&&n&&k)
    {
        memset(vis,0,sizeof(vis));
        memset(G,0,sizeof(G));
        ecnt=ans=0;
        int u,v,w;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            addEdge(u,v,w);
            addEdge(v,u,w);
        }
        dc(1);
        printf("%d\n",ans);
    }
    return 0;
}

[BZOJ2152]聪聪可可

给出一颗树,用分数表示出树上所有点对的距离和等于是 3 的倍数的概率。

n2×104

难点在分数……

/**************************************************************
    Problem: 2125
    User: zhangche0526
    Language: C++
    Result: Accepted
    Time:432 ms
    Memory:21816 kb
****************************************************************/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<stack> 
#include<queue>

const int MAXN=1e4+5;

int N,M,Q;

int abs(int x){return (x<0)?(-x):x;}

struct E{int next,to,val;} e[MAXN<<3];int ecnt,G[MAXN];
void addEdge(int u,int v,int w){e[++ecnt]=(E){G[u],v,w};G[u]=ecnt;}
void addEdge2(int u,int v,int w){addEdge(u,v,w);addEdge(v,u,w);}
void initCFS(){memset(G,0,sizeof(G));ecnt=0;}
struct A{int u,v,w;A(int u=0,int v=0,int w=0):u(u),v(v),w(w){};};

int dis[MAXN];bool inQ[MAXN];
std::queue<int> que;
void SPFA(int S)
{
    int i;
    memset(dis,0x3f,sizeof(dis));
    que.push(S);dis[S]=0;inQ[S]=true;
    while(!que.empty())
    {
        int u=que.front();que.pop();
        inQ[u]=false;
        for(i=G[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(dis[v]>dis[u]+e[i].val)
            {
                dis[v]=dis[u]+e[i].val;
                if(!inQ[v]) que.push(v),inQ[v]=true;
            }
        }
    }
}

std::stack<A> st;
int ringLen[MAXN],rcnt;
int belong[MAXN],ringRtDis[MAXN];
int anc[MAXN][20];
void addRing(int u,int v)
{
    rcnt++;
    while(st.top().u!=u&&st.top().v!=v)
    {
        A a=st.top();st.pop();
        ringRtDis[a.u]=ringRtDis[a.v]+a.w;
        ringLen[rcnt]+=a.w;
        if(a.u!=u) belong[a.u]=rcnt,anc[a.u][0]=u;
        if(a.v!=u) belong[a.v]=rcnt,anc[a.v][0]=u;
    }
    A a=st.top();st.pop();
    ringRtDis[a.u]=ringRtDis[a.v]+a.w;
    ringLen[rcnt]+=a.w;
    anc[a.v][0]=a.u;
}

int dfn[MAXN],low[MAXN],dcnt;
void tarjan(int u,int la)
{
    dfn[u]=low[u]=++dcnt;
    for(int i=G[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v==la) continue;
        if(!dfn[v])
        {
            st.push(A(u,v,e[i].val));
            tarjan(v,u);
            low[u]=std::min(low[u],low[v]);
            if(low[v]>=dfn[u])
                addRing(u,v);
        }else if(dfn[v]<low[u]) low[u]=dfn[v],st.push(A(u,v,e[i].val));
    }
}

int dpt[MAXN];
int rebuild(int u,int la)
{
    dpt[u]=dpt[la]+1;
    for(int i=G[u];i;i=e[i].next)
        rebuild(e[i].to,u);
}

inline void initLCA()
{
    for(int i=1;(1<<i)<=N;i++)
        for(int j=1;j<=N;j++)
            anc[j][i]=anc[anc[j][i-1]][i-1];
}

int calDis(int x,int y)
{
    int i;
    if(dpt[x]<dpt[y]) std::swap(x,y);
    int xDis=dis[x],yDis=dis[y];
    int maxlogn=std::floor(std::log(N)/std::log(2));
    for(i=maxlogn;i>=0;i--)
        if(dpt[x]-(1<<i)>=dpt[y])
            x=anc[x][i];
    if(x==y) return xDis-dis[x];
    for(i=maxlogn;i>=0;i--)
        if(anc[x][i]!=anc[y][i])
            x=anc[x][i],y=anc[y][i];
    if(belong[x]&&belong[x]==belong[y])
    {
        int xyDis=abs(ringRtDis[x]-ringRtDis[y]);
        int minDis=std::min(xyDis,ringLen[belong[x]]-xyDis);
        return xDis+yDis-dis[x]-dis[y]+minDis;
    }else return xDis+yDis-2*dis[anc[x][0]];
}

int main()
{   
    int i;
    scanf("%d%d%d",&N,&M,&Q);
    for(i=1;i<=M;i++)
    {
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        addEdge2(u,v,w);
    }
    SPFA(1);
    tarjan(1,0);
    initLCA();
    initCFS();
    for(i=2;i<=N;i++)
        addEdge(anc[i][0],i,0);
    rebuild(1,0);
    while(Q--)
    {
        int x,y;scanf("%d%d",&x,&y);
        printf("%d\n",calDis(x,y));
    }
    return 0;
}

动态点分治

[BZOJ1095][ZJOI2007]Hide 捉迷藏

有一棵树,初始时每个点都染成黑色,有两种操作:

  • 将一个点的颜色取反;
  • 查询距离最远的两个黑点的距离。

点数 N105 ,操作数 Q5×105 .

我们发现:这道题如果没有更改操作的话,就是一道裸的点分治题目。有了更改操作以后,显然我们每一次更改之后都跑一次点分治是不现实的,那么为了充分利用信息,我们考虑将点分治过程中的重心建成一棵重心树(可以证明:中心树的节点数等于原树,且深度不大于 log2n ),然后对于每一个重心开两个堆 B,C ,以动态维护信息。

操作一个重心时,我们将子树上每一个点父重心的距离压进这个重心的 C 堆中,然后将这一中心 C 堆堆顶元素压进其的 B 堆中,也就是说:每个重心的 B 堆存的是其以各儿子为根的子树中到此重心距离的大小;那么我们只要在全局开一个 A 堆,将所有重心的 B 堆的堆顶压进去后, A 堆的堆顶就是答案了。这样,我们只要在更改一个点的颜色时对它和它在重心树上的所有祖先进行一次更新即可。

好了,现在只剩下一个问题了:传统的堆是不支持删除操作的,我们需要使用一种可删堆。具体做法是:开两个堆, extH,delH ,分别表示存在的节点和被删除的节点,删除节点就把它压入 delH 中,取堆顶的时候判断一下是否被删除了,再弹出即可。

因为这道题荒废了半天

/**************************************************************
    Problem: 1095
    User: zhangche0526
    Language: C++
    Result: Accepted
    Time:18448 ms
    Memory:135668 kb
****************************************************************/

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>

const int MAXN=1e5+5;

int N,Q;
int fa[MAXN];
bool isOff[MAXN];int offNum;

struct E{int next,to;} e[MAXN<<1];int ecnt,G[MAXN];
void addEdge(int u,int v){e[++ecnt]=(E){G[u],v};G[u]=ecnt;}
void addEdge2(int u,int v){addEdge(u,v),addEdge(v,u);}

class HEAP
{
    private:
        std::priority_queue<int> extH,delH;
        void clean()
        {
            while(!delH.empty()&&extH.top()==delH.top())
            {
                extH.pop();
                delH.pop();
            }
        }
    public:
        void push(int x){extH.push(x);}
        void del(int x){delH.push(x);}
        void pop(){clean();extH.pop();}
        int top(){clean();return extH.top();}
        int scd()
        {
            int t1=top();pop();
            int t2=top();push(t1);
            return t2;
        }
        int size(){return extH.size()-delH.size();}
} A,B[MAXN],C[MAXN];

class LCA
{
    int anc[MAXN][20],dpt[MAXN];
    void dfs(int u,int la)
    {
        for(int i=G[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(v==la) continue;
            dpt[v]=dpt[u]+1;
            anc[v][0]=u;
            dfs(v,u);        
        }
    }
    public:
    void init()
    {
        dfs(1,0);
        for(int i=1;(1<<i)<=N;i++)
            for(int j=1;j<=N;j++)
                anc[j][i]=anc[anc[j][i-1]][i-1];
    }

    int calLCA(int x, int y)
    {
        int i;
        if(dpt[x]<dpt[y]) std::swap(x,y);
        int maxlogn=std::floor(std::log(N)/std::log(2));
        for(i=maxlogn;i>=0;i--)
            if(dpt[x]-(1<<i)>=dpt[y])
                x=anc[x][i];
        if(x==y) return x;
        for(i=maxlogn;i>=0;i--)
        {
            if(anc[x][i]!=anc[y][i])
            {
                x=anc[x][i];
                y=anc[y][i];
            }
        }
        return anc[x][0];
    }

    int calDis(int x,int y)
    {return dpt[x]+dpt[y]-2*dpt[calLCA(x,y)];}
} lca;

class PDC
{
    private:
        int vis[MAXN],rt;
        int mx[MAXN],sz[MAXN],min;
        void calSz(int u,int la)
        {
            sz[u]=1;mx[u]=0;
            for(int i=G[u];i;i=e[i].next)
            {
                int v=e[i].to;
                if(v!=la&&!vis[v])
                {
                    calSz(v,u);
                    sz[u]+=sz[v];
                    mx[u]=std::max(mx[u],sz[v]);
                }
            }
        }
        void calRt(int r,int u,int la)
        {
            mx[u]=std::max(mx[u],sz[r]-sz[u]);
            if(mx[u]<min) min=mx[u],rt=u;
            for(int i=G[u];i;i=e[i].next)
            {
                int v=e[i].to;
                if(v!=la&&!vis[v])calRt(r,v,u);
            }
        }
        void addC(int u,int la)
        {
            C[rt].push(lca.calDis(u,fa[rt]));
            for(int i=G[u];i;i=e[i].next)
            {      
                int v=e[i].to;                                            
                if(v!=la&&!vis[v])
                    addC(v,u);
            }
        }
    public:
        void build(int u,int laRt)
        {
            min=N;
            calSz(u,0);
            calRt(u,u,0);
            vis[rt]=true;fa[rt]=laRt;
            if(laRt) addC(rt,0);
            if(C[rt].size()) B[laRt].push(C[rt].top());
            int oRt=rt;
            for(int i=G[rt];i;i=e[i].next)
            {
                int v=e[i].to;
                if(!vis[v]) build(v,oRt); 
            }
        }
} ptDC;

void turnOn(int x)
{
    for(int i=x;fa[i];i=fa[i])
    {
        if(B[fa[i]].size()>=2) A.del(B[fa[i]].top()+B[fa[i]].scd());
        if(C[i].size()) B[fa[i]].del(C[i].top());
        C[i].del(lca.calDis(fa[i],x));
        if(C[i].size()) B[fa[i]].push(C[i].top());
        if(B[fa[i]].size()>=2) A.push(B[fa[i]].top()+B[fa[i]].scd());
    }
}
void turnOff(int x)
{
    for(int i=x;fa[i];i=fa[i])
    {
        if(B[fa[i]].size()>=2) A.del(B[fa[i]].top()+B[fa[i]].scd());
        if(C[i].size()) B[fa[i]].del(C[i].top());
        C[i].push(lca.calDis(fa[i],x));
        if(C[i].size()) B[fa[i]].push(C[i].top());
        if(B[fa[i]].size()>=2) A.push(B[fa[i]].top()+B[fa[i]].scd());
    }
}

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

int main()
{
    int i;
    offNum=N=read();
    for(i=1;i<=N;i++) isOff[i]=true;
    for(i=1;i<N;i++)
    {
        int u,v;u=read();v=read();
        addEdge2(u,v);
    }
    lca.init();
    ptDC.build(1,0);
    for(i=1;i<=N;i++)
        if(B[i].size()>=2)
        {
            A.push(B[i].top()+B[i].scd());
        }
    Q=read();
    char opt[10];
    while(Q--)
    {
        scanf("%s",opt);
        if(opt[0]=='G')
        {
            if(offNum==0) printf("-1\n");
            else if(offNum==1) printf("-1\n");
            else printf("%d\n",A.top());
        }
        else
        {
            int x=read();
            if(isOff[x])
            {
                isOff[x]=false;
                offNum--;
                turnOn(x);
            }else
            {
                isOff[x]=true;
                offNum++;
                turnOff(x);
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值