• 点分治与动态点分治学习笔记


    点分治

    点分治是处理树上路径的一类有力算法。

    分治算法我们都经常用到,平时我们在序列上可以直接分治。

    但是如果在树上怎么办呢?

    我们可以指定一个根递归下去处理子树。

    将原来的数分成许多子树,对每个子树分别处理

    如果我们随缘指定一个根,递归层数可能是(n)一级别的,复杂度显然会退化。

    借鉴我们在分治序列时我们每次取(mid)的思想。

    于是我们在分治树时我们每次取树的重心。

    树的重心(x)定义为树上的所有点中,删除此节点后最大子树(siz)最小的一个点

    计算方法就是简单的(dfs)

    //total为总大小
    void GetRoot(int p,int fa)
    {
        hson[p]=0,siz[p]=1;
        for(int i=h[p],v=e[i].v;~i;i=e[i].next,v=e[i].v)
            if((fa!=v)&&!vis[v])
                GetRoot(v,p),siz[p]+=siz[v],hson[p]=max(hson[p],siz[v]);
        hson[p]=max(hson[p],total-siz[p]);
        if(hson[p]<hson[Hroot])Hroot=p;
    }
    

    于是我们在点分治过程中每次选取子树重心为子树的根,

    这样就保证了递归层数不超过(logn)层。

    大致算法流程

    void solve(int x)
    {
    	vis[x]=1;
    	work();
    	for(遍历x的出边x->v)
            if(!vis[v])
            {
               	GetRoot(v);
                solve(Hroot);
            }
    }
    

    这个算法的难点主要在(work())上,其他的主要是框架。

    接下来介绍几道例题作为入门。

    题目介绍

    (1).洛谷P3806【模板】点分治

    题目描述

    给定一棵有(n)个点的树

    询问树上距离为(k)的点对是否存在。

    思想分析

    考虑每个路径分为两类,经过根的与不经过根的。

    (dis_i)(i)到当前根的距离。

    所有经过根的路径(u,v)的长度都可以表示为(dis_u+dis_v)

    对于不经过根的路径,它一定在根的某个子树中,我们对子树处理一次就可以了。

    于是算法的分治思想已经呼之欲出了。

    我们对于每个节点统计经过根的路径,然后对子树分治

    如何统计经过根的路径?

    我们对于每个点开一个(map),记录子树中的点到根的(dis),扫描子树时,对于子树中的每个(dis_i),看看有没有(k-dis_i)就可以辣。

    (set)也可以,似乎快一点的样子。

    手写哈希没人拦你

    复杂度(O(nlog^2n))可以稳稳通过本题((nle10000))

    使用(hash)应该可以降到(O(nlogn))

    update:emm好像直接开也开的下来着

    代码实现

    /*
    @Date    : 2019-08-29 21:10:53
    @Author  : Adscn (adscn@qq.com)
    @Link    : https://www.cnblogs.com/LLCSBlog
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define IL inline
    #define RG register
    #define gi getint()
    #define gc getchar()
    #define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
    IL int getint()
    {
    	RG int xi=0;
    	RG char ch=gc;
    	bool f=0;
    	while(ch<'0'||ch>'9')ch=='-'?f=1:f,ch=gc;
    	while(ch>='0'&&ch<='9')xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
    	return f?-xi:xi;
    }
    template<typename T>
    IL void pi(T k,char ch=0)
    {
    	if(k<0)k=-k,putchar('-');
    	if(k>=10)pi(k/10,0);
    	putchar(k%10+'0');
    	if(ch)putchar(ch);
    }
    const int MAXN=10007;
    struct Edge{
        int v,next,w;
    }e[MAXN<<1];
    int h[MAXN],cnt,n,m,siz[MAXN],hson[MAXN],Hroot;
    char vis[MAXN],ans[107];
    int stk[MAXN],top,K[107],dis[MAXN],total;
    set<int>f;
    IL void add(int u,int v,int w){e[++cnt]=(Edge){v,h[u],w};h[u]=cnt;}
    IL void GetRoot(int p,int fa)
    {
        hson[p]=0,siz[p]=1;
        for(int i=h[p],v=e[i].v;~i;i=e[i].next,v=e[i].v)
            if((fa^v)&&!vis[v])
                GetRoot(v,p),siz[p]+=siz[v],hson[p]=max(hson[p],siz[v]);
        hson[p]=max(hson[p],total-siz[p]);
        if(hson[p]<hson[Hroot])Hroot=p;
    }
    IL void dfs(int p,int fa)
    {
        stk[++top]=dis[p];
        for(int i=h[p],v=e[i].v;~i;i=e[i].next,v=e[i].v)
            if((fa^v)&&!vis[v])
                dis[v]=dis[p]+e[i].w,dfs(v,p);
    }
    IL void Query(int x){for(int i=m;i;i--)ans[i]|=*f.lower_bound(K[i]-x)==K[i]-x;}
    IL void solve(int p)
    {
        vis[p]=1;
        f.clear();
        f.insert(0);
        for(int i=h[p],v=e[i].v;~i;i=e[i].next,v=e[i].v)
            if(!vis[v]){
                top=0,dis[v]=e[i].w;
                dfs(v,p);
                for(int i=top;i;--i)Query(stk[i]);
                for(int i=top;i;--i)f.insert(stk[i]);
            }
        for(int i=h[p],v=e[i].v;~i;i=e[i].next,v=e[i].v)
            if(!vis[v])
            {
                Hroot=0;
                total=siz[v];
                GetRoot(v,0);
                solve(Hroot);
            }
    }
    int main(void)
    {
        fill(h,h+MAXN+1,-1);
        n=gi,m=gi;
        for(int i=1,a,b,c;i<n;++i)a=gi,b=gi,c=gi,add(a,b,c),add(b,a,c);
        for(int i=m;i;--i)K[i]=gi;
        hson[Hroot=0]=total=n,GetRoot(1,0),solve(Hroot);
        for(int i=m;i;--i)puts(ans[i]?"AYE":"NAY");
        return 0;
    }
    

    (2).洛谷P2634 [国家集训队]聪聪可可

    这题其实有简单好写复杂度又低的树形dp做法

    题目大意

    求长度被(3)整除的路径条数

    ((a,b))((b,a))视为两条

    思想分析

    根据套路

    我们要统计经过根(rt)的长度为(3)的倍数的路径。

    记录路径长度模(3)(0,1,2)的个数。

    (pre[3])为访问(rt)的子树(v)之前的记录,(now[3])为当前子树(v)的记录。

    贡献为(2(pre[0]*now[0]+pre[1]*now[2]+pre[2]*now[1]+now[0]))

    然后访问(v)后把(now)累加到(pre)中即可。

    最后的(ans)要记得(+n)

    因为每个点也算一条路径。

    代码实现

    /*
    @Date    : 2019-08-29 21:39:48
    @Author  : Adscn (adscn@qq.com)
    @Link    : https://www.cnblogs.com/LLCSBlog
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define IL inline
    #define RG register
    #define gi getint()
    #define gc getchar()
    #define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
    template<typename T>IL bool chkmax(T &x,const T &y){return x<y?x=y,1:0;}
    template<typename T>IL bool chkmin(T &x,const T &y){return x>y?x=y,1:0;}
    IL int getint()
    {
    	RG int xi=0;
    	RG char ch=gc;
    	bool f=0;
    	while(!isdigit(ch))ch=='-'?f=1:f,ch=gc;
    	while(isdigit(ch))xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
    	return f?-xi:xi;
    }
    template<typename T>
    IL void pi(T k,char ch=0)
    {
    	if(k<0)k=-k,putchar('-');
    	if(k>=10)pi(k/10,0);
    	putchar(k%10+'0');
    	if(ch)putchar(ch);
    }
    #define For_tree(x) for(register int i=head[x],v=e[i].v;~i;i=e[i].nxt,v=e[i].v)
    const int N=2e4+7;
    struct edge{
    	int v,nxt,w;
    }e[N<<1];
    int head[N],cnt;
    inline void add(int u,int v,int w){e[++cnt]=(edge){v,head[u],w},head[u]=cnt;}
    inline void init(){memset(head,cnt=-1,sizeof head);}
    int n;
    bool vis[N];
    int siz[N],hson[N],dis[N];
    int pre[3],now[3];
    int total,Hroot;
    int ans;
    inline void groot(int x,int fa){
    	siz[x]=1,hson[x]=0;
    	For_tree(x)
    		if(v^fa&&!vis[v])
    			groot(v,x),siz[x]+=siz[v],chkmax(hson[x],siz[v]);
    	chkmax(hson[x],total-siz[x]);
    	if(hson[x]<hson[Hroot])Hroot=x;
    }
    inline void gdis(int x,int fa){
    	++now[dis[x]%3];
    	For_tree(x)
    		if(v^fa&&!vis[v])
    			dis[v]=dis[x]+e[i].w,gdis(v,x);
    }
    inline void work(int x){
    	For_tree(x)
    		if(!vis[v])
    		{
    			now[0]=now[1]=now[2]=0;
    			dis[v]=e[i].w,gdis(v,x);
    			ans+=2*(pre[0]*now[0]+pre[1]*now[2]+pre[2]*now[1]+now[0]);
    			pre[0]+=now[0],pre[1]+=now[1],pre[2]+=now[2];
    		}
    }
    inline void solve(int x){
    	vis[x]=1;
    	work(x);
    	pre[0]=pre[1]=pre[2]=0;
    	For_tree(x)
    		if(!vis[v]){
    			total=siz[v],Hroot=0;
    			groot(v,x),solve(Hroot);
    		}
    }
    inline int gcd(int a,int b){return b?gcd(b,a%b):a;}
    int main(void)
    {
    	init();
    	n=gi;
    	for(int i=1,u,v,w;i<n;++i)u=gi,v=gi,w=gi,add(u,v,w),add(v,u,w);
    	hson[Hroot=0]=total=n;
    	groot(1,0),solve(Hroot);
    	ans+=n;
    	int sum=n*n;
    	int Gcd=gcd(ans,sum);
    	cout<<ans/Gcd<<"/"<<sum/Gcd;
    	return 0;
    }
    

    思想分析2

    其实我们可以每个节点(x)求出其子树内的(dis%3)的余数记录数组(m),经过该点的路径数即为(2*m[1]*m[2]+m[0]^2)

    但是这样计算(x)时,对于子树(v),我们计算(v)中的答案时重复计算了既经过(x)这个点,又经过(v)这一个点的路径,于是我们要容斥减去它。

    这个时候就不要(+n)(qwq)

    详见代码

    代码实现2

    /*
    @Date    : 2019-08-30 07:55:55
    @Author  : Adscn (adscn@qq.com)
    @Link    : https://www.cnblogs.com/LLCSBlog
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define IL inline
    #define RG register
    #define gi getint()
    #define gc getchar()
    #define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
    template<typename T>IL bool chkmax(T &x,const T &y){return x<y?x=y,1:0;}
    template<typename T>IL bool chkmin(T &x,const T &y){return x>y?x=y,1:0;}
    IL int getint()
    {
    	RG int xi=0;
    	RG char ch=gc;
    	bool f=0;
    	while(!isdigit(ch))ch=='-'?f=1:f,ch=gc;
    	while(isdigit(ch))xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
    	return f?-xi:xi;
    }
    template<typename T>
    IL void pi(T k,char ch=0)
    {
    	if(k<0)k=-k,putchar('-');
    	if(k>=10)pi(k/10,0);
    	putchar(k%10+'0');
    	if(ch)putchar(ch);
    }
    #define For_tree(x) for(register int i=head[x],v=e[i].v;~i;i=e[i].nxt,v=e[i].v)
    const int N=2e4+7;
    struct edge{
    	int v,nxt,w;
    }e[N<<1];
    int head[N],cnt;
    inline void add(int u,int v,int w){e[++cnt]=(edge){v,head[u],w},head[u]=cnt;}
    inline void init(){memset(head,cnt=-1,sizeof head);}
    int n;
    bool vis[N];
    int siz[N],hson[N],dis[N];
    int m[3];
    int total,Hroot;
    int ans;
    inline void groot(int x,int fa){
    	siz[x]=1,hson[x]=0;
    	For_tree(x)
    		if(v^fa&&!vis[v])
    			groot(v,x),siz[x]+=siz[v],chkmax(hson[x],siz[v]);
    	chkmax(hson[x],total-siz[x]);
    	if(hson[x]<hson[Hroot])Hroot=x;
    }
    inline void gdis(int x,int fa){
    	++m[dis[x]%3];
    	For_tree(x)
    		if(v^fa&&!vis[v])
    			dis[v]=dis[x]+e[i].w,gdis(v,x);
    }
    inline int work(int x,int distan){
    	dis[x]=distan;
    	m[0]=m[1]=m[2]=0;
    	gdis(x,0);
    	return m[0]*m[0]+2*m[1]*m[2];
    }
    inline void solve(int x){
    	ans+=work(x,0);
    	vis[x]=1;
    	For_tree(x)
    		if(!vis[v]){
    			ans-=work(v,e[i].w);
    			total=siz[v],Hroot=0;
    			groot(v,x),solve(Hroot);
    		}
    }
    inline int gcd(int a,int b){return b?gcd(b,a%b):a;}
    int main(void)
    {
    	init();
    	n=gi;
    	for(int i=1,u,v,w;i<n;++i)u=gi,v=gi,w=gi,add(u,v,w),add(v,u,w);
    	hson[Hroot=0]=total=n;
    	groot(1,0),solve(Hroot);
    //	ans+=n;
    	int sum=n*n;
    	int Gcd=gcd(ans,sum);
    	cout<<ans/Gcd<<"/"<<sum/Gcd;
    	return 0;
    }
    

    如果你非常顺利的做完了上面的题目,恭喜你,你已经成功点分治入门辣!

    接下来我们开始进阶部分(QwQ)


    (3.)洛谷P2664 树上游戏

    题目大意

    给定树上每个点的颜色。

    定义(s(i,j))(i)(j)的颜色数量。

    (sum_i=sumlimits_{j=1}^ns(i,j))

    求出所有的(sum_i)

    思想分析

    想不到吧,这题也有简单的(O(n))做法

    根据套路,

    我们要在(O(n))的时间统计经过根的路径的点对的答案。

    我们首先要想到对于树上的一条路径,每种颜色只有出现的第一个颜色有贡献。

    我们考虑每个点到根的路径

    对于树上的一个点(v)

    如果(v)的颜色是(v)到根(x)的路径上第一次出现,

    (v)的颜色会对(x)的其它子树中的点(u)贡献(siz_v)

    但是如果(u)(x)的路径上也有这个颜色,我们就重复计算了。

    于是我们就把它扫一遍,减掉。

    (Another=siz[x]-siz[v])

    统计时(ans+=Sum+num*Another)

    (Sum)是上面写的贡献总和,(num)(j)到根上路径的颜色数,不包括根。

    (Another)是其他子树大小。

    处理子树时要把当前的子树贡献从总体中去掉,之后再加回来。

    具体细节见代码。

    代码实现

    略长。

    /*
    @Date    : 2019-08-30 08:28:48
    @Author  : Adscn (adscn@qq.com)
    @Link    : https://www.cnblogs.com/LLCSBlog
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define IL inline
    #define RG register
    #define gi getint()
    #define gc getchar()
    #define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
    template<typename T>IL bool chkmax(T &x,const T &y){return x<y?x=y,1:0;}
    template<typename T>IL bool chkmin(T &x,const T &y){return x>y?x=y,1:0;}
    IL int getint()
    {
    	RG int xi=0;
    	RG char ch=gc;
    	bool f=0;
    	while(!isdigit(ch))ch=='-'?f=1:f,ch=gc;
    	while(isdigit(ch))xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
    	return f?-xi:xi;
    }
    template<typename T>
    IL void pi(T k,char ch=0)
    {
    	if(k<0)k=-k,putchar('-');
    	if(k>=10)pi(k/10,0);
    	putchar(k%10+'0');
    	if(ch)putchar(ch);
    }
    #define For_tree(x) for(register int i=head[x],v=e[i].v;~i;i=e[i].nxt,v=e[i].v)
    const int N=1e5+7;
    typedef long long ll;
    struct edge{
    	int v,nxt;
    }e[N<<1];
    int head[N],edge_cnt;
    inline void init(){memset(head,edge_cnt=-1,sizeof head);}
    inline void add(int u,int v){e[++edge_cnt]=(edge){v,head[u]},head[u]=edge_cnt;}
    bool vis[N];
    int hson[N],siz[N],Hroot,total,col[N],color[N],cnt[N],Sum,n,Another,num;
    ll ans[N];
    inline void groot(int x,int fa){
    	siz[x]=1,hson[x]=0;
    	For_tree(x)
    		if(v^fa&&!vis[v])
    			groot(v,x),siz[x]+=siz[v],chkmax(hson[x],siz[v]);
    	chkmax(hson[x],total-siz[x]);
    	if(hson[x]<hson[Hroot])Hroot=x;
    }
    inline void modify(int x,int fa,int val){
    	++cnt[col[x]],siz[x]=1;
    	For_tree(x)if(v^fa&&!vis[v])modify(v,x,val),siz[x]+=siz[v];
    	if(cnt[col[x]]==1)Sum+=siz[x]*val,color[col[x]]+=siz[x]*val;
    	--cnt[col[x]];
    }
    inline void clear(int x,int fa){
    	cnt[col[x]]=color[col[x]]=0;
    	For_tree(x)if(v^fa&&!vis[v])clear(v,x);
    }
    inline void count(int x,int fa)
    {
    	++cnt[col[x]];
    	if(cnt[col[x]]==1)Sum-=color[col[x]],++num;
    	ans[x]+=Sum+1ll*num*Another;
    	For_tree(x)if(v^fa&&!vis[v])count(v,x);
    	if(cnt[col[x]]==1)Sum+=color[col[x]],--num;
    	--cnt[col[x]];
    }
    inline void work(int x){
    	modify(x,0,1);
    	ans[x]+=Sum-color[col[x]]+siz[x];
    	++cnt[col[x]];
    	For_tree(x)
    		if(!vis[v])
    		{
    			Sum-=siz[v],color[col[x]]-=siz[v],modify(v,x,-1);
    			Another=siz[x]-siz[v];
    			count(v,x);
    			Sum+=siz[v],color[col[x]]+=siz[v],modify(v,x,1);	
    		}
    	--cnt[col[x]],Sum=num=0;
    	clear(x,0);
    }
    inline void solve(int x){
    	vis[x]=1;
    	work(x);
    	For_tree(x)
    		if(!vis[v])
    		{
    			Hroot=0,total=siz[v];
    			groot(v,x);
    			solve(Hroot);
    		}
    }
    int main(void)
    {
    	init();
    	n=gi;
    	for(int i=1;i<=n;++i)col[i]=gi;
    	for(int i=1,u,v;i<n;++i)u=gi,v=gi,add(u,v),add(v,u);
    	hson[Hroot=0]=total=n;
    	groot(1,0);
    	solve(Hroot);
    	for(int i=1;i<=n;++i)pi(ans[i],'
    ');
    	return 0;
    }
    

    动态点分治

    你可以选择先看一下周书予dalao的博客。

    简单地说就是通过重心重建一颗点分树来支持修改。

    显然点分树的深度是(logn)级别的。

    修改的时候就跳点分树修改。

    点分树满足一些性质,这也是动态点分治(点分树)处理问题的基础

    性质1:点分树上两点的lca​一定在原树两点的路径上

    性质2:点分治上的一个子树总是原树上的一个连通块

    这两个性质画画图很容易理解,但是一定要牢记,不然写题目的时候可能发现自己的方法是伪的

    先看几道题目吧。

    因为一些原因,难度是降序的(因为这就是我的开题顺序)

    题目介绍

    (1).幻想乡战略游戏

    题目大意

    给你一颗树,边上有距离。

    每个点上有点权(D_v),初始为0。

    你要选择一个点(u),最小化(sumlimits_{vin tree} D_v*dis(u,v))

    每次会修改点权,询问这个最小值。

    思想分析

    假设当前补给站是(x),并以(x)为根,(v)(x)的一个子节点,设(sumdis_i)(i)子树中到(x)的距离和 。

    那么显然(v)(x)更优当且仅当(2*sumdis_v>sumdis_x)

    因为假如补给站移动到(v),增长,总距离会缩短(sumdis_v*e(x,v))然后增加((sumdis_x-sumdis_v)*e(x,v))

    移项就可以了。

    如果不存在更优的(v),(x)就是最优位置。

    我们维护三个数组

    (sumv[x])表示点分树上以x为根的子树点权总和

    (dis1[x])表示点分树上以x为根的子树到达x的代价和

    (dis2[x])表示点分树上以x为根的子树到达(fa_x)的代价和

    当我们在点分树上移动时。

    假设我们从一个点(x)移动到(fa_x)

    我们要计算(fa_x)其他的子树的贡献,这个改变了(dis(x,fa[x])*(sumv[fa[x]]-sumv[x]))

    考虑(x)子树中的贡献,于是还要减掉原来的子树的(dis2),加上新的(dis1)

    这个比较好理解。

    代码比较好敲(qwq)

    代码实现

    /*
    @Date    : 2019-08-30 17:51:05
    @Author  : Adscn (adscn@qq.com)
    @Link    : https://www.cnblogs.com/LLCSBlog
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define IL inline
    #define RG register
    #define gi getint()
    #define gc getchar()
    #define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
    template<typename T>IL bool chkmax(T &x,const T &y){return x<y?x=y,1:0;}
    template<typename T>IL bool chkmin(T &x,const T &y){return x>y?x=y,1:0;}
    IL int getint()
    {
    	RG int xi=0;
    	RG char ch=gc;
    	bool f=0;
    	while(!isdigit(ch))ch=='-'?f=1:f,ch=gc;
    	while(isdigit(ch))xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
    	return f?-xi:xi;
    }
    template<typename T>
    IL void pi(T k,char ch=0)
    {
    	if(k<0)k=-k,putchar('-');
    	if(k>=10)pi(k/10,0);
    	putchar(k%10+'0');
    	if(ch)putchar(ch);
    }
    const int N=1e5+7;
    #define For_tree(x) for(int i=head[x],v=e[i].v;~i;i=e[i].nxt,v=e[i].v)
    #define Fortree(x) for(int i=Head[x],v=E[i].v;~i;i=E[i].nxt,v=E[i].v)
    typedef long long ll;
    struct Edge{
    	int v,nxt,w;
    }e[N<<1],E[N<<1];
    int head[N],Head[N],cnt,cnt_;
    int first[N<<1],ver[N<<1],depth_ver[N<<1],dfn;
    int f[21][N<<1],n,m,s;
    bool vis[N];
    int siz[N],hson[N],dis[N],lg2[N<<2],total,Hrt;
    int par[N];
    ll dis1[N],dis2[N],sumv[N];
    inline void init(){
    	memset(head,cnt=-1,sizeof head);
    	memset(Head,cnt_=-1,sizeof Head);
    	lg2[0]=-1;
    	for(int i=1;i<(N<<2);++i)lg2[i]=lg2[i>>1]+1;
    }
    IL void add(int u,int v,int w)
    {
    	e[++cnt]=(Edge){v,head[u],w};
    	head[u]=cnt;
    }
    IL void Add(int u,int v,int w)
    {
    	E[++cnt_]=(Edge){v,Head[u],w};
    	Head[u]=cnt_;
    }
    IL void dfs(int x,int dep)
    {
    	vis[x]=1,first[x]=++dfn,ver[dfn]=x,depth_ver[dfn]=dep;
    	For_tree(x)
    		if(!vis[v])
    		{
    			dis[v]=dis[x]+e[i].w;
    			dfs(v,dep+1);
    			ver[++dfn]=x,depth_ver[dfn]=dep;
    		}
    }
    IL void STinit(int k)
    {
    	int len=lg2[k];
    	for(RG int i=1;i<=k;i++)f[0][i]=i;
    	for(RG int i=1;i<=len;i++)
    		for(RG int j=1;j+(1<<i)-1<=k;j++)
    			f[i][j]=(depth_ver[f[i-1][j]]<=depth_ver[f[i-1][j+(1<<(i-1))]])?f[i-1][j]:f[i-1][j+(1<<(i-1))];
    }
    IL int STQuery(int l,int r)
    {
    	int k=lg2[r-l+1];
    	return depth_ver[f[k][l]]<=depth_ver[f[k][r-(1<<k)+1]]?f[k][l]:f[k][r-(1<<k)+1];
    }
    IL int LCA(int u,int v)
    {
    	if(first[u]>first[v])swap(u,v);
    	return ver[STQuery(first[u],first[v])];
    }
    IL int Dis(int u,int v){
    	return dis[u]+dis[v]-2*dis[LCA(u,v)];
    }
    inline void grt(int x,int fa){
    	siz[x]=1,hson[x]=0;
    	For_tree(x)
    		if(v^fa&&!vis[v])
    			grt(v,x),siz[x]+=siz[v],chkmax(hson[x],siz[v]);
    	chkmax(hson[x],total-siz[x]);
    	if(hson[x]<hson[Hrt])Hrt=x;
    }
    inline void presolve(int x,int fa){
    	vis[x]=1,par[x]=fa;
    	For_tree(x)
    		if(!vis[v]){
    			Hrt=0,total=siz[v];
    			grt(v,0),Add(x,Hrt,v);
    			presolve(Hrt,x);
    		}
    }
    inline void modify(int x,int val){
    	sumv[x]+=val;
    	for(int i=x;par[i];i=par[i]){
    		ll dis=Dis(x,par[i]);
    		dis1[par[i]]+=dis*val;
    		dis2[i]+=dis*val;
    		sumv[par[i]]+=val;
    	}
    }
    inline ll work(int x){
    	ll ans=dis1[x];
    	for(int i=x;par[i];i=par[i]){
    		int dis=Dis(x,par[i]);
    		ans+=dis1[par[i]]-dis2[i];
    		ans+=dis*(sumv[par[i]]-sumv[i]);
    	}
    	return ans;
    }
    inline ll query(int x){
    	ll ans=work(x);
    	Fortree(x)if(work(E[i].w)<ans)return query(v);
    	return ans;
    }
    int main(void)
    {
    	init();
    	int n=gi,m=gi;
    	for(int i=1,u,v,w;i<n;++i)u=gi,v=gi,w=gi,add(u,v,w),add(v,u,w);
    	dfs(1,0);
    	memset(vis,0,sizeof vis);
    	STinit(dfn);
    	hson[Hrt=0]=total=n;
    	grt(1,0);int tmp=Hrt;
    	presolve(Hrt,0);
    	Hrt=tmp;
    	while(m--){
    		int x=gi,y=gi;
    		modify(x,y);
    		pi(query(Hrt),'
    ');
    	}
    	return 0;
    }
    

    (2.)BZOJ3730震波

    题目大意

    给你一颗树,边长均为(1),点有点权。

    两个操作

    (1.)询问离(x)不超过(k)的点的点权之和。

    (2.)单点修改点权

    强制在线。

    思想分析

    ps:以下均指点分树。

    我们对每个节点开一颗动态开点线段树,维护子树中的点权和。

    具体的,以距离为下标,值为点权和。

    修改时跳点分树,直接改。

    查询的时候跳到父亲,查询一下[0,k-dis]的和。

    但是我们跳点分树的时候会重复计算原子树的贡献。

    再开个线段树记录一下子树对父亲的贡献,每次减掉就可以了。

    这题就不用ST表求LCA了,复杂度是一样的,但是这题卡时间

    (O(nlog^2n))

    此题卡时间,丧心病狂。

    线段树常数大没法做,把线段树改成BIT就可以,但我懒得写了(qwq)

    码量仍然不大

    代码实现

    /*
    @Date    : 2019-09-01 09:02:02
    @Author  : Adscn (adscn@qq.com)
    @Link    : https://www.cnblogs.com/LLCSBlog
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define IL inline
    #define RG register
    #define gi getint()
    #define gc getchar()
    #define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
    template<typename T>IL bool chkmax(T &x,const T &y){return x<y?x=y,1:0;}
    template<typename T>IL bool chkmin(T &x,const T &y){return x>y?x=y,1:0;}
    IL int getint()
    {
    	RG int xi=0;
    	RG char ch=gc;
    	bool f=0;
    	while(!isdigit(ch))ch=='-'?f=1:f,ch=gc;
    	while(isdigit(ch))xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
    	return f?-xi:xi;
    }
    template<typename T>
    IL void pi(T k,char ch=0)
    {
    	if(k<0)k=-k,putchar('-');
    	if(k>=10)pi(k/10,0);
    	putchar(k%10+'0');
    	if(ch)putchar(ch);
    }
    const int N=1e5+7;
    namespace segtree{
    	struct sgt{int ls,rs,val;}tree[N*150];
    	int root[N<<1],node_cnt;
    	#define mid ((l+r)>>1)
    	inline void modify(int &rt,int l,int r,const int &pos,const int &val){
    		if(!rt)rt=++node_cnt;
    		tree[rt].val+=val;
    		if(l==r)return;
    		if(pos<=mid)modify(tree[rt].ls,l,mid,pos,val);
    		else modify(tree[rt].rs,mid+1,r,pos,val);
    	}
    	inline int query(int rt,int l,int r,const int &L,const int &R){
    		if(!rt)return 0;
    		if(L<=l&&r<=R)return tree[rt].val;
    		int ret=0;
    		if(L<=mid)ret+=query(tree[rt].ls,l,mid,L,R);
    		if(R>mid)ret+=query(tree[rt].rs,mid+1,r,L,R);
    		return ret;
    	}
    	#undef mid
    }
    using segtree::root;
    #define For_tree(x) for(register int i=head[x],v=e[i].v;~i;i=e[i].nxt,v=e[i].v)
    struct edge{int v,nxt;}e[N<<1];
    int head[N],cnt;
    int lg2[N<<1];
    int f[18][N<<1],vis[N],first[N],depth[N],dfn,siz[N],hson[N],Hrt,total,par[N],val[N],n,m;
    inline void init(){
    	memset(head,cnt=-1,4*n+4);
    	lg2[0]=-1;
    	for(int i=1;i<=(n<<1);++i)lg2[i]=lg2[i>>1]+1;
    }
    inline void add(int u,int v){e[++cnt]=(edge){v,head[u]},head[u]=cnt;}
    inline void dfs(int x,int dep){
    	vis[x]=1,first[x]=++dfn,f[0][dfn]=dep;
    	depth[x]=dep;
    	For_tree(x)
    		if(!vis[v])
    			dfs(v,dep+1),f[0][++dfn]=dep;
    }
    inline void STinit(int k)
    {
    	int len=lg2[k];
    	for(int i=1;i<=len;++i)
    		for(int j=1;j+(1<<i)-1<=k;++j)
    			f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
    }
    inline int STDisQuery(int l,int r){
    	if(l>r)swap(l,r);
    	return min(f[lg2[r-l+1]][l],f[lg2[r-l+1]][r-(1<<lg2[r-l+1])+1]);
    }
    inline int Dis(int u,int v){return depth[u]+depth[v]-2*STDisQuery(first[u],first[v]);}
    void grt(int x,int fa=0){
    	siz[x]=1,hson[x]=0;
    	For_tree(x)
    		if(v^fa&&!vis[v])
    			grt(v,x),siz[x]+=siz[v],chkmax(hson[x],siz[v]);
    	chkmax(hson[x],total-siz[x]);
    	if(hson[x]<hson[Hrt])Hrt=x;
    }
    void presolve(int x,int fa=0)
    {
    	vis[x]=1,par[x]=fa;
    	For_tree(x)
    		if(!vis[v]){
    			Hrt=0,total=siz[v];
    			grt(v),presolve(Hrt,x);
    		}
    }
    inline void modify(int x,int val)
    {
    	segtree::modify(root[x],0,n-1,0,val);
    	for(int i=x;par[i];i=par[i])
    	{
    		int dis=Dis(x,par[i]);
    		segtree::modify(root[par[i]],0,n-1,dis,val);
    		segtree::modify(root[i+n],0,n-1,dis,val);
    	}
    }
    inline int query(int x,int k){
    	int ret=segtree::query(root[x],0,n-1,0,k);
    	for(int i=x;par[i];i=par[i])
    	{
    		int dis=Dis(x,par[i]);
    		if(dis<=k)ret+=segtree::query(root[par[i]],0,n-1,0,k-dis)-segtree::query(root[i+n],0,n-1,0,k-dis);
    	}
    	return ret;
    }
    int main(void)
    {
    	n=gi,m=gi;
    	init();
    	for(int i=1;i<=n;++i)val[i]=gi;
    	for(int i=1,u,v;i<n;++i)u=gi,v=gi,add(u,v),add(v,u);
    	dfs(1,0),STinit(dfn);
    	memset(vis,0,4*n+4);
    	hson[Hrt=0]=total=n;
    	grt(1),presolve(Hrt);
    	for(int i=1;i<=n;++i)modify(i,val[i]);
    	int lastans=0;
    	while(m--){
    		int opt=gi,x=gi^lastans,y=gi^lastans;
    		if(!opt)pi(lastans=query(x,y),'
    ');
    		else modify(x,y-val[x]),val[x]=y;
    	}
    	return 0;
    }
    

    3.BZOJP4372烁烁的游戏

    题目大意

    给你一颗树,边长均为(1),点有点权。

    两个操作

    (1.)增加离(x)不超过(d)的点的点权一个值(w)

    (2.)询问点(x)的点权

    思想分析

    这个就是上面的题目改一下,

    线段树存离(d)的修改量就可以了。

    变成了区间修改,单点查询。

    代码实现

    /*
    @Date    : 2019-09-02 06:25:31
    @Author  : Adscn (adscn@qq.com)
    @Link    : https://www.cnblogs.com/LLCSBlog
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define IL inline
    #define RG register
    #define gi getint()
    #define gc getchar()
    #define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
    template<typename T>IL bool chkmax(T &x,const T &y){return x<y?x=y,1:0;}
    template<typename T>IL bool chkmin(T &x,const T &y){return x>y?x=y,1:0;}
    IL int getint()
    {
    	RG int xi=0;
    	RG char ch=gc;
    	bool f=0;
    	while(!isdigit(ch))ch=='-'?f=1:f,ch=gc;
    	while(isdigit(ch))xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
    	return f?-xi:xi;
    }
    template<typename T>
    IL void pi(T k,char ch=0)
    {
    	if(k<0)k=-k,putchar('-');
    	if(k>=10)pi(k/10,0);
    	putchar(k%10+'0');
    	if(ch)putchar(ch);
    }
    const int N=1e5+7;
    namespace segtree{
    	struct sgt{int ls,rs,val;}tree[N*150];
    	int root[N<<1],node_cnt;
    	#define mid ((l+r)>>1)
    	inline void modify(int &rt,int l,int r,const int &L,const int &R,const int &val){
    		if(!rt)rt=++node_cnt;
    		if(L<=l&&r<=R){tree[rt].val+=val;return;}
    		if(L<=mid)modify(tree[rt].ls,l,mid,L,R,val);
    		if(R>mid) modify(tree[rt].rs,mid+1,r,L,R,val);
    	}
    	inline int query(int rt,int l,int r,const int &pos){
    		if(!rt)return 0;
    		if(l==r)return tree[rt].val;
    		int ret=tree[rt].val;
    		if(pos<=mid)ret+=query(tree[rt].ls,l,mid,pos);
    		else ret+=query(tree[rt].rs,mid+1,r,pos);
    		return ret;
    	}
    	#undef mid
    }
    using segtree::root;
    #define For_tree(x) for(register int i=head[x],v=e[i].v;~i;i=e[i].nxt,v=e[i].v)
    struct edge{int v,nxt;}e[N<<1];
    int head[N],cnt;
    int lg2[N<<1];
    int f[18][N<<1],vis[N],first[N],depth[N],dfn,siz[N],hson[N],Hrt,total,par[N],val[N],n,m;
    inline void init(){
    	memset(head,cnt=-1,4*n+4);
    	lg2[0]=-1;
    	for(int i=1;i<=(n<<1);++i)lg2[i]=lg2[i>>1]+1;
    }
    inline void add(int u,int v){e[++cnt]=(edge){v,head[u]},head[u]=cnt;}
    inline void dfs(int x,int dep){
    	vis[x]=1,first[x]=++dfn,f[0][dfn]=dep;
    	depth[x]=dep;
    	For_tree(x)
    		if(!vis[v])
    			dfs(v,dep+1),f[0][++dfn]=dep;
    }
    inline void STinit(int k)
    {
    	int len=lg2[k];
    	for(int i=1;i<=len;++i)
    		for(int j=1;j+(1<<i)-1<=k;++j)
    			f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
    }
    inline int STDisQuery(int l,int r){
    	if(l>r)swap(l,r);
    	return min(f[lg2[r-l+1]][l],f[lg2[r-l+1]][r-(1<<lg2[r-l+1])+1]);
    }
    inline int Dis(int u,int v){return depth[u]+depth[v]-2*STDisQuery(first[u],first[v]);}
    void grt(int x,int fa=0){
    	siz[x]=1,hson[x]=0;
    	For_tree(x)
    		if(v^fa&&!vis[v])
    			grt(v,x),siz[x]+=siz[v],chkmax(hson[x],siz[v]);
    	chkmax(hson[x],total-siz[x]);
    	if(hson[x]<hson[Hrt])Hrt=x;
    }
    void presolve(int x,int fa=0)
    {
    	vis[x]=1,par[x]=fa;
    	For_tree(x)
    		if(!vis[v]){
    			Hrt=0,total=siz[v];
    			grt(v),presolve(Hrt,x);
    		}
    }
    inline void modify(int x,int d,int val)
    {
    	segtree::modify(root[x],0,n-1,0,d,val);
    	for(int i=x;par[i];i=par[i])
    	{
    		int dis=Dis(x,par[i]);
    		if(dis>d)continue;
    		segtree::modify(root[par[i]],0,n-1,0,d-dis,val);
    		segtree::modify(root[i+n],0,n-1,0,d-dis,val);
    	}
    }
    inline int query(int x){
    	int ret=segtree::query(root[x],0,n-1,0);
    	for(int i=x;par[i];i=par[i])
    	{
    		int dis=Dis(x,par[i]);
    		ret+=segtree::query(root[par[i]],0,n-1,dis)-segtree::query(root[i+n],0,n-1,dis);
    	}
    	return ret;
    }
    int main(void)
    {
    	n=gi,m=gi;
    	init();
    	for(int i=1,u,v;i<n;++i)u=gi,v=gi,add(u,v),add(v,u);
    	dfs(1,0),STinit(dfn);
    	memset(vis,0,4*n+4);
    	hson[Hrt=0]=total=n;
    	grt(1),presolve(Hrt);
    	int lastans=0;
    	while(m--){
    		char opt=gc;
    		int x=gi;
    		if(opt=='Q')pi(query(x),'
    ');
    		else
    		{
    			int d=min(gi,n),w=gi;
    			modify(x,d,w);
    		}
    	}
    	return 0;
    }
    

    4.QTREE5

    (luogu)(remote judge)似乎炸了,所以上面放的是(vjudge)

    这题还有(lct)做法,具体看我的另一篇博客

    题目大意

    你被给定一棵n个点的树,点从1到n编号。每个点可能有两种颜色:黑或白。我们定义dist(a,b)为点a至点b路径上的边个数。

    一开始所有的点都是黑色的。

    要求作以下操作:

    0 i 将点i的颜色反转(黑变白,白变黑)

    1 v 询问dist(u,v)的最小值。u点必须为白色(u与v可以相同),显然如果v是白点,查询得到的值一定是0。

    特别地,如果作'1'操作时树上没有白点,输出-1.

    思想分析

    对于每个点我们开一个(multiset)存一下它离点分树中白点的距离,以及编号

    然后跳点分树找就可以了

    用堆的话,查询复杂度可以做到(nlogn),因为查询堆顶是(O(1))

    但是我懒

    代码实现

    /*
    @Date    : 2019-09-12 10:45:46
    @Author  : Adscn (adscn@qq.com)
    @Link    : https://www.cnblogs.com/LLCSBlog
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define IL inline
    #define RG register
    #define gi getint()
    #define gc getchar()
    #define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
    template<typename T>IL bool chkmax(T &x,const T &y){return x<y?x=y,1:0;}
    template<typename T>IL bool chkmin(T &x,const T &y){return x>y?x=y,1:0;}
    IL int getint()
    {
    	RG int xi=0;
    	RG char ch=gc;
    	bool f=0;
    	while(!isdigit(ch))ch=='-'?f=1:f,ch=gc;
    	while(isdigit(ch))xi=xi*10+ch-48,ch=gc;
    	return f?-xi:xi;
    }
    template<typename T>
    IL void pi(T k,char ch=0)
    {
    	if(k<0)k=-k,putchar('-');
    	if(k>=10)pi(k/10,0);
    	putchar(k%10+'0');
    	if(ch)putchar(ch);
    }
    #define Fortree(x) for(int i=head[x],v=e[i].v;~i;i=e[i].nxt,v=e[i].v)
    #define fi first
    #define se second
    #define mp make_pair
    typedef pair<int,int> pii;
    typedef multiset<pii> Mi;
    const int N=2e5+7;
    struct edge{
    	int v,nxt;
    }e[N<<1];
    int head[N],cnt;
    int siz[N],lg2[N],dfn,f[20][N<<1],depth[N],first[N],vis[N],hson[N],total,Hrt,par[N],col[N],tot;
    Mi h[N];
    inline void init(){
    	memset(head,cnt=-1,sizeof head);
    	lg2[0]=-1;
    	for(int i=1;i<N;++i)lg2[i]=lg2[i>>1]+1;
    }
    inline void add(int u,int v){e[++cnt]=(edge){v,head[u]},head[u]=cnt;}
    inline void dfs(int x,int dep){
    	vis[x]=1,first[x]=++dfn,f[0][dfn]=depth[x]=dep;
    	Fortree(x)if(!vis[v])dfs(v,dep+1),f[0][++dfn]=dep;
    	vis[x]=0;
    }
    inline void STinit(int k){
    	for(int i=1;i<=lg2[k];++i)
    		for(int j=1;j+(1<<i)-1<=k;++j)
    			f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
    }
    inline int STQuery(int l,int r)
    {
    	if(l>r)swap(l,r);
    	int k=lg2[r-l+1];
    	return min(f[k][l],f[k][r-(1<<k)+1]);
    }
    inline int Dis(int u,int v){return depth[u]+depth[v]-(STQuery(first[u],first[v])<<1);}
    inline void grt(int x,int fa=0){
    	siz[x]=1,hson[x]=0;
    	Fortree(x)
    		if(v^fa&&!vis[v])
    			grt(v,x),siz[x]+=siz[v],chkmax(hson[x],siz[v]);
    	chkmax(hson[x],total-siz[x]);
    	if(hson[x]<hson[Hrt])Hrt=x;
    }
    inline void presolve(int x,int fa=0)
    {
    	vis[x]=1,par[x]=fa;
    	Fortree(x)
    		if(v^fa&&!vis[v])
    			Hrt=0,total=siz[v],grt(v),presolve(Hrt,x);
    }
    inline void Modify(int x){
    	(col[x]^=1)?++tot:--tot;
    	if(col[x])for(int i=x;i;i=par[i])h[i].insert(mp(Dis(i,x),x));
    	else for(int i=x;i;i=par[i])h[i].erase(mp(Dis(i,x),x));
    }
    inline int Query(int x){
    	if(!tot)return -1;
    	int ret=INT_MAX;
    	for(int i=x;i;i=par[i])if(!h[i].empty())chkmin(ret,Dis(x,h[i].begin()->se));
    	return ret;
    }
    int main(void)
    {
    	int n=gi;
    	init();
    	for(int i=1,u,v;i<n;++i)u=gi,v=gi,add(u,v),add(v,u);
    	dfs(1,0),STinit(dfn);
    	hson[Hrt=0]=total=n,grt(1),presolve(Hrt);
    	for(int i=1,m=gi;i<=m;++i){
    		int opt=gi,x=gi;
    		if(opt^1)Modify(x);
    		else pi(Query(x),'
    ');
    	}
    	return 0;
    }
    

    总结

    点分治的经典应用是询问树上某些满足条件的点对。

    套路是容斥,

    但如果你沿着子树逐个处理,其实就不用容斥。

    但是有时候逐个合并的复杂度过高,你就不能去一个个处理,只能容斥(但我还真没做过这样的题目)。

    动态点分治经典的应用是询问一个点到其他很多特定点的信息。(并支持修改)

    比如

    • 询问一个点到所有黑点的距离/距离不超过(k)的点权和

    • 询问对于一个点(u),有多少(v),满足某些条件。

    动态点分治的套路是用数据结构维护题目所给定的条件,有时候需要容斥。

  • 相关阅读:
    System.BadImageFormatException: 未能加载文件或程序集""或它的某一个依赖项。试图加载格式不正确的程序。
    Win10中解决SYSTEM权限获取,删Windows old
    Jenkins配置MSBuild编译.net4.6的项目
    TeamViewer11的安全设置
    Jenkins配置的邮件无法发送的问题
    Jenkins 2.x版本修改启动端口号(Windows)
    Jenkins 2.x版本的安装步骤(Windows)
    CruiseControl.NET/CCNET安装包下载
    Jenkins+CCNET的另类部署图
    VisualSVN Server和Subversion的联系
  • 原文地址:https://www.cnblogs.com/LLCSBlog/p/11432352.html
Copyright © 2020-2023  润新知