【树】点分治和点分树

学习资料:OI WiKi树分治

点分治

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

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

作用

处理树的大量路径信息。

做法(模板)

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

大致代码:

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

#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\) 的点对是否存在。\(1\le n\le10^4,\,m\le100\)

\(1\le w\le10000\)\(1\le k\le10^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,\,1\le n,a_i\le2\times10^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),1\le k\le200000\)

\(g(k)\neq0\) 时,输出 \(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\n",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\rm{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\n",ans);
        }
        else
        {
            solveop1(u,u,v-val[u]);
            val[u]=v;
        }
    }
}

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

大致题意:有 \(n\) \((1\le n\le100000)\) 个点的无根树, 点有点权, 边有边权, 且权值 \(v\leq 10000\)

\(m\) \((1\le m\le100000)\) 个询问:

\(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\n",ans>=inf?-1:ans<<1);
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/kkkek/p/12784072.html