zoukankan      html  css  js  c++  java
  • 【树】点分治和点分树

    点分治

    学习资料:OI WiKi树分治

    时间复杂度 (mathcal{O(nlogn)})

    ​ 这里是指 (solve(;))函数需要 (mathcal{O(n)}),再加上分治的 (logn)。所以前提是 (solve())函数的复杂度不要太糟糕。

    作用

    处理树的大量路径信息。

    做法(模板)

    OI WiKi: 我们先随意选择一个节点作为根节点 (rt) ,所有完全位于其子树中的路径可以分为两种,一种是经过当前根节点的路径,一种是不经过当前根节点的路径。对于经过当前根节点的路径,又可以分为两种,一种是以根节点为一个端点的路径,另一种是两个端点都不为根节点的路径。而后者又可以由两条属于前者链合并得到。所以,对于枚举的根节点 (rt) ,我们先计算在其子树中且经过该节点的路径对答案的贡献,再递归其子树对不经过该节点的路径进行求解。

    (code:)

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    const int maxn=2e5+5;
    
    void read(int &x)
    {
        char c;
        while(!isdigit(c=getchar()));x=c-'0';
        while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-'0';
    }
    
    vector<int>p[maxn];
    
    int root,Tsiz,maxsiz;
    int siz[maxn],sonsiz[maxn];
    /*
    root是每回的树根,重心求后做根
    Tsiz是当前树的大小,即siz[u](假设u是当前树的根节点)
    maxsiz初始化要inf,之后再求重心
    sonsiz是重儿子的大小
    */
    bool vis[maxn];//vis是用来标记做过根的点(扫过的点)
    void getG(int u,int f)//求重心,重心做为树根,确保总体复杂度的那个logn
    {
        siz[u]=1;sonsiz[u]=0;
        for(int v:p[u])
        {
            if(v==f||vis[v])continue;
            getG(v,u);
            siz[u]+=siz[v];
            sonsiz[u]=max(sonsiz[u],siz[v]);
        }
        sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
        if(sonsiz[u]<maxsiz)
        {
            root=u;maxsiz=sonsiz[u];
        }
    }
    void getval(int u,int fa)
    {
        /*
        做需要的计算
        */
        for(int v:p[u])
        {
            if(v==fa||vis[v])continue;
            getval(v,u);
        }
    }
    void solve(int u)
    {
        //处理以u为根的数据
    }
    void divide(int u)//分治
    {
        vis[u]=1;
        solve(u);
        for(int v:p[u])
        {
            if(vis[v])continue;
            maxsiz=inf;Tsiz=siz[v];getG(v,0);
            divide(root);
        }
    }
    

    例题

    1,luoguP3806【模板】点分治1

    给定一棵有 (n) 个点的树,(m) 次询问树上距离为 (k) 的点对是否存在。(1le nle10^4,\,mle100)

    (1le wle10000)(1le kle10^7)

    (code:)

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    const int mod=1e9+7;
    const int maxn=1e4+5;
    
    void read(int&x)
    {
        char ch;
        while(!isdigit(ch=getchar()));x=ch-'0';
        while(isdigit(ch=getchar()))x=(x<<3)+(x<<1)+ch-'0';
    }
    struct P{
        int u,w;
    };
    vector<P>p[maxn];
    int Tsiz,siz[maxn],sonsiz[maxn],maxsiz;
    bool vis[maxn];
    int root;
    void getG(int u,int f)
    {
        siz[u]=1;sonsiz[u]=0;
        for(P poi:p[u])
        {
            if(poi.u==f||vis[poi.u])continue;
            getG(poi.u,u);
            siz[u]+=siz[poi.u];
            sonsiz[u]=max(sonsiz[u],siz[poi.u]);
        }
        sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
        if(sonsiz[u]<maxsiz)
        {
            root=u;maxsiz=sonsiz[u];
        }
    }
    bool is[10000007];
    int m,q[110],ans[110];
    int dis[maxn],cnt,val[maxn];
    void get(int u,int fa,int w)
    {
        if(w>10000000)return;
        dis[++cnt]=w;val[u]=w;
        for(P poi:p[u])
            if(poi.u!=fa&&!vis[poi.u])
                get(poi.u,u,w+poi.w);
    }
    void clear(int u,int fa)
    {
        is[val[u]]=0;val[u]=0;
        for(P poi:p[u])
            if(poi.u!=fa&&!vis[poi.u])
                clear(poi.u,u);
    }
    void solve(int u)
    {
        is[0]=1;
        for(P poi:p[u])
        {
            if(vis[poi.u])continue;
            cnt=0;get(poi.u,u,poi.w);
            for(int j=1;j<=m;j++)
                for(int i=1;i<=cnt&&!ans[j];i++)
                    if(dis[i]<=q[j]&&is[q[j]-dis[i]])ans[j]=1;
            for(int i=1;i<=cnt;i++)is[dis[i]]=1;
        }
        clear(u,0);
    }
    void divide(int u)
    {
        vis[u]=1;
        solve(u);
        for(P poi:p[u])
        {
            if(vis[poi.u])continue;
            maxsiz=inf;Tsiz=siz[poi.u];getG(poi.u,0);
            divide(root);
        }
    }
    int main()
    {
        int n,u,v,w;
        read(n);read(m);
        for(int i=1;i<n;i++)
        {
            read(u);read(v);read(w);
            p[u].push_back(P{v,w});p[v].push_back(P{u,w});
        }
        for(int i=1;i<=m;i++)read(q[i]);
        Tsiz=n;maxsiz=inf;getG(1,0);divide(root);
        for(int i=1;i<=m;i++)
        {
            if(ans[i])puts("AYE");
            else puts("NAY");
        }
    }
    

    2,CF990G

    给定一棵有 (n) 个点的树,点有点权 (a_i,\,1le n,a_ile2 imes10^5)

    (f(x,y)=gcd(a_x,a_y))

    设函数 (g(k))

    [g(k)=sum_{x=1}^n sum_{y=x}^n [f(x,y)==k] ]

    请求出所有 (g(k),1le kle200000)

    (g(k) eq0) 时,输出 (g(k)) 的值。

    (code:)

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    const int mod=1e9+7;
    const int maxn=2e5+5;
    
    void read(int&x)
    {
        char ch;
        while(!isdigit(ch=getchar()));x=ch-'0';
        while(isdigit(ch=getchar()))x=(x<<3)+(x<<1)+ch-'0';
    }
    int a[maxn];
    vector<int>p[maxn];
    int Tsiz,siz[maxn],sonsiz[maxn],maxsiz;
    bool vis[maxn];
    int root;
    void getG(int u,int f)
    {
        siz[u]=1;sonsiz[u]=0;
        for(int v:p[u])
        {
            if(v==f||vis[v])continue;
            getG(v,u);
            siz[u]+=siz[v];
            sonsiz[u]=max(sonsiz[u],siz[v]);
        }
        sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
        if(sonsiz[u]<maxsiz)
        {
            root=u;maxsiz=sonsiz[u];
        }
    }
    ll ans[maxn];
    map<int,int>mp,tmp;
    void get(int u,int fa,int w)
    {
        tmp[w]++;
        for(int v:p[u])
        {
            if(v==fa||vis[v])continue;
            get(v,u,__gcd(w,a[v]));
        }
    }
    void solve(int u)
    {
        mp.clear();ans[a[u]]++;
        for(int v:p[u])
        {
            if(vis[v])continue;
            tmp.clear();get(v,u,__gcd(a[v],a[u]));
            for(auto it:tmp)
            {
                ans[it.first]+=it.second;
                for(auto itt:mp)
                    ans[__gcd(it.first,itt.first)]+=1ll*it.second*itt.second;
            }
            for(auto it:tmp)mp[it.first]+=it.second;
        }
    }
    void divide(int u)
    {
        vis[u]=1;
        solve(u);
        for(int v:p[u])
        {
            if(vis[v])continue;
            maxsiz=inf;Tsiz=siz[v];getG(v,0);
            divide(root);
        }
    }
    int main()
    {
        int n,u,v;
        read(n);
        for(int i=1;i<=n;i++)read(a[i]);
        for(int i=1;i<n;i++)
        {
            read(u);read(v);
            p[u].push_back(v);p[v].push_back(u);
        }
        Tsiz=n;maxsiz=inf;getG(1,0);divide(root);
        for(int i=1;i<=200000;i++)
            if(ans[i])printf("%d %lld
    ",i,ans[i]);
    }
    


    点分树

    点分树是通过更改原树形态使树的层数变成稳定 (log\,n) 的一种重构树 。

    常用于解决与原树形态无关的带修改问题。

    时间复杂度

    重构树的层数约是 (log\,n) 层,(约?因为菊花图是1层.. 类推) ,设查询一个点的时间复杂度是 (T_1),则 (T=T_1log\,n)

    比如线段树+点分树,则时间复杂度是 (mathcal{O}(n(logn)^2))

    核心模板

    bool vis[maxn];
    int root,Tsiz,maxsiz,siz[maxn],sonsiz[maxn];
    void getG(int u,int f)
    {
        siz[u]=1;sonsiz[u]=0;
        for(int v:p[u])
        {
            if(v==f||vis[v])continue;
            getG(v,u);
            siz[u]+=siz[v];
            sonsiz[u]=max(sonsiz[u],siz[v]);
        }
        sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
        if(sonsiz[u]<maxsiz)
        {
            root=u;maxsiz=sonsiz[u];
        }
    }
    int buildTree(int u,int f,int si)//最外层u任选,f置0,si为 n
    {
        Tsiz=si;maxsiz=inf;getG(u,0);
        int g=root,tg,tsi;
        vis[g]=1;fa[g]=f;tsiz[g]=si;
        for(int v:p[g])
        {
            if(vis[v])continue;
            tsi=siz[v]<siz[g]?siz[v]:(si-siz[g]);
            tg=buildTree(v,g,tsi);
            np[g].push_back(tg);
        }
        build(g,g);//这个是线段树的build,视具体操作
        return g;
    }
    

    例题

    1,BZOJ震波(luoguP6329【模板】点分树|震波) ((2s,250 m{M}))

    大致题意:有 (n) 个点的无根树,每个点有点权,接下来要在线处理 (m) 个操作:

    (0;x;k) :输出点 (x) 距离小于等于 (k) 内的所有点的权值和。

    (1;x;y):表示把 点 (x) 的权值修改为 (y)

    在线:数据的 (x;y;k;) 需要异或上一次的答案。初始答案为 (0)

    做法:

    点分树+动态开点线段树+st表求lca

    因为点分树是 (log\,n) 层 ,所以所有子树 点的个数之和 约为 (nlog\,n) , 所以对于每个点动态开点的建线段树,其空间复杂度还是很可观的。

    至于 (st) 表求 (lca) , 因为 (st) 表求 (lca) 相比倍增和树剖求的 (lca) 来说,是最快的。

    建两种线段树,点权均是距离, 其中 (T_1) 表示点 (x) 的子树内, 与 (x) 距离为 (d) 的点的点权和, (T_2)表示点 (fa[x]) 的子树内,与 (fa[x]) 距离为 (d) 的点的点权和。

    (code:)

    #pragma GCC optimize("-O2")
    #include<bits/stdc++.h>
    #define reg register
    #define min(a,b) (a)>(b)?(b):(a)
    using namespace std;
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    const int mod=1e9+7;
    const int maxn=2e5+5;
    
    void read(int&x)
    {
        char c;
        while(!isdigit(c=getchar()));x=c-'0';
        while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-'0';
    }
    int val[maxn];
    vector<int>p[maxn],np[maxn];
    int dep[maxn],depth[maxn<<1],id[maxn],rid[maxn<<1],cnt,st[maxn<<1][25];
    void dfs(int u,int fa,int d)
    {
        id[u]=++cnt;rid[cnt]=u;depth[cnt]=d;dep[u]=d;
        for(int v:p[u])
        {
            if(v==fa)continue;
            dfs(v,u,d+1);
            rid[++cnt]=u;depth[cnt]=d;
        }
    }
    int lg[maxn<<1];
    void init()
    {
        lg[0]=-1;
        for(int i=1;i<=cnt;i++)lg[i]=lg[i>>1]+1;
        for(int i=1;i<=cnt;i++)st[i][0]=i;
        for(int j=1;(1<<j)<=cnt;j++)
            for(int i=1;i+(1<<j)-1<=cnt;i++)
                st[i][j]=depth[st[i][j-1]]<depth[st[i+(1<<j-1)][j-1]]?
                         st[i][j-1]:
                         st[i+(1<<j-1)][j-1];
    }
    int lca(int u,int v)
    {
        if(id[u]>id[v])swap(u,v);
        int s=id[u],t=id[v],len=lg[t-s+1];
        return depth[st[s][len]]<depth[st[t-(1<<len)+1][len]]?rid[st[s][len]]:rid[st[t-(1<<len)+1][len]];
    }
    int dis(int u,int v){return dep[u]+dep[v]-(dep[lca(u,v)]<<1);}
    int T1[maxn],T2[maxn],tot;
    int L[maxn<<4],R[maxn<<4],sum[maxn<<4];
    void add(int &rt,int l,int r,int x,int v)
    {
        if(!rt)rt=++tot;
        sum[rt]+=v;
        if(l==r)return;
        int mid=(l+r)>>1;
        if(x<=mid)add(L[rt],l,mid,x,v);
        else add(R[rt],mid+1,r,x,v);
    }
    int getv(int rt,int l,int r,int ll,int rr)
    {
        if(!rt||ll>r||rr<l)return 0;
        if(ll<=l&&r<=rr)return sum[rt];
        int mid=(l+r)>>1;
        return getv(L[rt],l,mid,ll,rr)+getv(R[rt],mid+1,r,ll,rr);
    }
    
    int fa[maxn],tsiz[maxn];
    void build(int u,int g)
    {
        add(T1[g],0,tsiz[g]-1,dis(u,g),val[u]);
        if(fa[g])add(T2[g],0,tsiz[fa[g]]-1,dis(u,fa[g]),val[u]);
        for(int v:np[u])
            if(v!=fa[u])build(v,g);
    }
    bool vis[maxn];
    int root,Tsiz,maxsiz,siz[maxn],sonsiz[maxn];
    void getG(int u,int f)
    {
        siz[u]=1;sonsiz[u]=0;
        for(int v:p[u])
        {
            if(v==f||vis[v])continue;
            getG(v,u);
            siz[u]+=siz[v];
            sonsiz[u]=max(sonsiz[u],siz[v]);
        }
        sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
        if(sonsiz[u]<maxsiz)
        {
            root=u;maxsiz=sonsiz[u];
        }
    }
    int buildTree(int u,int f,int si)
    {
        Tsiz=si;maxsiz=inf;getG(u,0);
        int g=root,tg,tsi;
        vis[g]=1;fa[g]=f;tsiz[g]=si;
        for(int v:p[g])
        {
            if(vis[v])continue;
            tsi=siz[v]<siz[g]?siz[v]:(si-siz[g]);
            tg=buildTree(v,g,tsi);
            np[g].push_back(tg);
        }
        build(g,g);
        return g;
    }
    int solveop0(int u,int s,int k,int x)
    {
        int d=dis(u,x),ans=0;
        if(d<=k)
        {
            ans=getv(T1[u],0,tsiz[u]-1,0,k-d);
            if(s)ans-=getv(T2[s],0,tsiz[u]-1,0,k-d);
        }
        if(fa[u])ans+=solveop0(fa[u],u,k,x);
        return ans;
    }
    void solveop1(int u,int x,int w)//w=newval-val[u];val[u]=newval;
    {
        add(T1[u],0,tsiz[u]-1,dis(u,x),w);
        if(fa[u])
        {
            add(T2[u],0,tsiz[fa[u]]-1,dis(fa[u],x),w);
            solveop1(fa[u],x,w);
        }
    }
    void printtree(int u,int d)
    {
        printf("root%d=(%d,fa:%d) ",d,u,fa[u]);
        for(int v:np[u])
            if(v!=fa[u])printtree(v,d+1);
        putchar(10);
    }
    int main()
    {
        int n,m,u,v,op,ans=0;
        read(n);read(m);
        for(int i=1;i<=n;i++)read(val[i]);
        for(int i=1;i<n;i++)
        {
            read(u);read(v);
            p[u].push_back(v);p[v].push_back(u);
        }
        dfs(1,1,1);init();
        int vr=buildTree(1,0,n);
    //    printtree(vr,1);
        while(m--)
        {
            read(op);read(u);read(v);
            u^=ans;v^=ans;
            if(!op)
            {
                ans=solveop0(u,0,v,u);
                printf("%d
    ",ans);
            }
            else
            {
                solveop1(u,u,v-val[u]);
                val[u]=v;
            }
        }
    }
    

    2,牛客 苹果树 ((4s,512 m{M}))

    大致题意:有 (n) ((1le nle100000)) 个点的无根树, 点有点权, 边有边权, 且权值 (vleq 10000)

    (m) ((1le mle100000)) 个询问:

    (1;u;x):在点 (u) 处, 叠加一个点权为 (x) 的点;

    (2;u;x;y):询问从x出发,到点权值在 ([x,;y]) 范围内的一个点的最小距离花费。

    做法:

    点分树+动态开点线段树+st表求lca, 线段树的点值是点权, 线段树更新最小值,

    (ans=min(ans,dis(u,fa)+query(fa,l,r)))

    (coed:)

    #pragma GCC optimize("-O2")
    #include<bits/stdc++.h>
    #define reg register
    #define min(a,b) (a)>(b)?(b):(a)
    #define max(a,b) (a)>(b)?(a):(b)
    using namespace std;
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    const int mod=1e9+7;
    const int maxn=1e5+5;
    const int MAXN=2e7;
    
    void read(int&x)
    {
        char c;
        while(!isdigit(c=getchar()));x=c-'0';
        while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-'0';
    }
    struct P{
        int u,w;
    };
    const int up=10000;
    int val[maxn];
    struct EE{
        P to;int next;
    }egg[maxn<<1];
    int headd[maxn],eecnt;
    inline void add(int u,P v){egg[++eecnt]={v,headd[u]};headd[u]=eecnt;}
    struct E{
        int to,next;
    }eg[maxn<<1];
    int head[maxn],ecnt;
    inline void add(int u,int v){eg[++ecnt]={v,head[u]};head[u]=ecnt;}
    int dd[maxn],depth[maxn<<2],id[maxn],rid[maxn<<2],cnt,st[maxn<<2][25];
    void dfs(int u,int fa,int d,int w)
    {
        id[u]=++cnt;rid[cnt]=u;depth[cnt]=d;dd[u]=w;
        for(reg int i=headd[u];i;i=egg[i].next)
        {
            if(egg[i].to.u==fa)continue;
            dfs(egg[i].to.u,u,d+1,w+egg[i].to.w);
            rid[++cnt]=u;depth[cnt]=d;
        }
    }
    int lg[maxn<<2];
    void init()
    {
        lg[0]=-1;
        for(reg int i=1;i<=cnt;i++)lg[i]=lg[i>>1]+1;
        for(reg int i=1;i<=cnt;i++)st[i][0]=i;
        for(reg int j=1;(1<<j)<=cnt;j++)
            for(reg int i=1;i+(1<<j)-1<=cnt;i++)
                st[i][j]=depth[st[i][j-1]]<depth[st[i+(1<<j-1)][j-1]]?
                         st[i][j-1]:st[i+(1<<j-1)][j-1];
    }
    inline int lca(int u,int v)
    {
        if(id[u]>id[v])swap(u,v);
        reg int s=id[u],t=id[v],len=lg[t-s+1];
        return depth[st[s][len]]<depth[st[t-(1<<len)+1][len]]?
               rid[st[s][len]]:rid[st[t-(1<<len)+1][len]];
    }
    inline int dis(int u,int v){return dd[u]+dd[v]-(dd[lca(u,v)]<<1);}
    int T[maxn],tot,L[MAXN],R[MAXN],mi[MAXN];
    void add(int &rt,int l,int r,int x,int v)
    {
        if(!rt)rt=++tot,mi[rt]=inf;
        mi[rt]=min(mi[rt],v);
        if(l==r)return;
        int mid=(l+r)>>1;
        if(x<=mid)add(L[rt],l,mid,x,v);
        else add(R[rt],mid+1,r,x,v);
    }
    int getv(int rt,int l,int r,int ll,int rr)
    {
        if(!rt||ll>r||rr<l)return inf;
        if(ll<=l&&r<=rr)return mi[rt];
        int mid=(l+r)>>1,res;
        res=getv(L[rt],l,mid,ll,rr);
        if(R[rt]&&mi[R[rt]]<res)res=min(res,getv(R[rt],mid+1,r,ll,rr));
        return res;
    }
    int fa[maxn];
    void build(int u,int g)
    {
        add(T[g],1,up,val[u],dis(u,g));
        for(int i=head[u];i;i=eg[i].next)build(eg[i].to,g);
    }
    bool vis[maxn];
    int root,Tsiz,maxsiz,siz[maxn],sonsiz[maxn];
    void getG(int u,int f)
    {
        siz[u]=1;sonsiz[u]=0;
        for(reg int i=headd[u];i;i=egg[i].next)
        {
            if(egg[i].to.u==f||vis[egg[i].to.u])continue;
            getG(egg[i].to.u,u);
            siz[u]+=siz[egg[i].to.u];
            sonsiz[u]=max(sonsiz[u],siz[egg[i].to.u]);
        }
        sonsiz[u]=max(sonsiz[u],Tsiz-sonsiz[u]);
        if(sonsiz[u]<maxsiz)
        {
            root=u;maxsiz=sonsiz[u];
        }
    }
    int buildTree(int u,int f,int si)
    {
        Tsiz=si;maxsiz=inf;getG(u,0);
        int g=root,tg,tsi;
        vis[g]=1;fa[g]=f;
        for(reg int i=headd[g];i;i=egg[i].next)
        {
            if(vis[egg[i].to.u])continue;
            tsi=siz[egg[i].to.u]<siz[g]?siz[egg[i].to.u]:(si-siz[g]);
            tg=buildTree(egg[i].to.u,g,tsi);
            add(g,tg);
        }
        build(g,g);
        return g;
    }
    
    void solveop1(int u,int x,int w)
    {
        add(T[u],1,up,w,dis(u,x));
        if(fa[u])solveop1(fa[u],x,w);
    }
    int solveop2(int u,int x,int l,int r)
    {
        int ans=dis(u,x)+getv(T[u],1,up,l,r);
        if(fa[u])ans=min(ans,solveop2(fa[u],x,l,r));
        return ans;
    }
    int main()
    {
        int n,m,u,v,w,op,ans;
        read(n);read(m);
        for(reg int i=1;i<=n;i++)read(val[i]);
        for(reg int i=1;i<n;i++)
        {
            read(u);read(v);read(w);
            add(u,{v,w});add(v,{u,w});
        }
        dfs(1,0,1,0);init();
        buildTree(1,0,n);
        while(m--)
        {
            read(op);read(u);read(v);
            if(op==1)solveop1(u,u,v);
            else
            {
                read(w);
                ans=solveop2(u,u,v,w);
                printf("%d
    ",ans>=inf?-1:ans<<1);
            }
        }
    }
    
  • 相关阅读:
    Kubernetes节点维护
    Kubernetes helm配置国内镜像源
    windows universal app中使用mvvm light
    windows phone 开发常用小技巧
    异步编程中的最佳做法(Async/Await) --转
    windows phone 开发常用小技巧
    windows phone 开发常用小技巧
    windows phone 开发常用小技巧
    #假期归来# 看看国外开发者第一时间用swift写的Flappy Bird (2014.6.3)
    vs2013 TFS如何彻底删除团队项目
  • 原文地址:https://www.cnblogs.com/kkkek/p/12784072.html
Copyright © 2011-2022 走看看