关于树论【动态点分治】

搬运:题意传送门:http://caioj.cn/problem.php?id=1433 
前几天跟波* * * *老师一起搞这题,结果最后莫名其妙的被波老师D飞。。。 
我用到的是动态点分治。 
动态点分治就是基于树的重心(一棵树中,以重心为根的最大子树的节点最小)上的解法,该解法将一棵树分成若干棵子树,对每一棵子树进行重新分配,一直往下分直到每一颗树的节点数为1,上一层的重心需要连接下一层的重心,对这棵新树来进行分治递归求解。 
这道题的做法: 
对于新树每个节点我们用两个堆来维护,每个节点的第一个堆维护以自己为根的子树内所有节点到自己父亲节点的距离。 第二个堆维护每个子节点第一个堆的堆顶(就是每一个孩子节点的子树内里自己最远的距离) 那么相对于每一个节点,第二个堆的最大值加次大值就是子树内经过自己的最长链。 
对于全局,开一个堆来维护每一个节点第二个堆的最大值加次大值,那么全局的堆顶就是答案。 
至于求树上两点距离,应用了dfs序+st表的做法。dep[i]表示的是1到i节点的距离。x到y之间的距离=dep[x]+dep[y]-2*dep[最近公共祖先],那么如何来求解dep[最近公共祖先]呢 ,我用的是dfs序上ST表优化区间最小值,当然用倍增LCA也是可以的。 
显然x和y的最近公共祖先是x到y路径上深度最小的点,如果我们用什么方法把路径上的点表示为连续的编号,那么用ST表求区间最小值不就解决了吗。 
补充说明: 
每个堆分成两个堆。但改变颜色的操作,某些状态就不合法了。那么就需要删除,但是堆每次只可以删除堆顶,所以我们把每个堆分成两个堆,一个堆记录全部状态,一个堆记录没用的状态,这样我们每次询问堆顶的时候,如果堆顶是没用的状态,那么我们就要删除。 
对于不会st表的同学,请看这里。 
st表可以解决rmq(区间最值)问题,LCA的树上倍增算法也是类似于该思想,算法预处理时间复杂度O(nlogn),查询时间O(1),但不支持在线修改。 
利用DP的思想,得到一个数组mn[i][j],该数组表示从j到j+2^i-1的最小值,所以x到y的最小值表示为min(mn[t][x], mn[t][y-2^t+1])。 
关于继承的问题,mn[0][k]自然等于k自身的值,而mn[1][k]的值为min(mn [0][k], mn[0][k+1]),不难发现,mn[i][j]=min(mn[i-1][j], mn[i-1][j+2^(i-1)]。 
代码实现:

//Bin[i]表示2的i次方,Log[i]表示log(i)
Bin[0]=1;for(int i=1;i<20;i++)Bin[i]=Bin[i-1]*2;
Log[0]=-1;for(int i=1;i<=200000;i++)Log[i]=Log[i/2]+1;
for(int i=1;i<=n;i++)mn[0][i]=a[i];、
for(int i=1;i<=Log[n];i++)
    for(int j=1;j<=n;j++)
        if(j+Bin[i]-1<=n)mn[i][j]=min(mn[i-1][j],mn[i-1][j+Bin[i-1]]); 

感谢hanks_o(在度娘搜就行,ljhaoziyu刚Qtree7题的小伙伴)的倾情帮助和分析。

#include<queue>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
int Bin[20],Log[210000];//Bin[i]表示2的i次方,Log[i]就是log(i)
struct node
{
    int x,y,d,next;
}a[210000];int len,last[110000];
void ins(int x,int y,int d)
{
    len++;
    a[len].x=x;a[len].y=y;a[len].d=d;
    a[len].next=last[x];last[x]=len;
}
struct heap
{
    priority_queue<int> A,B;//A堆记录存在的状态,但因为堆不支持修改,另开一个B堆记录淘汰的状态
    void push (int x){A.push(x);}
    void erase(int x){B.push(x);}
    void pop()
    {
        while(B.size()&&A.top()==B.top()){A.pop();B.pop();}
        A.pop();
    }
    int size(){return A.size()-B.size();}
    int top()
    {
        while(B.size()&&A.top()==B.top()){A.pop();B.pop();}
        if(A.size()==0)return 0;
        return A.top();
    }
    int stop()
    {
        if(size()<2)return 0;
        int x=top();pop();
        int y=top();push(x);
        return y;
    }
}A,B[110000],C[110000];
/*
每个节点的C堆维护所有子树内的节点到自己父亲节点的距离
B堆维护所有子节点第一个堆的堆顶(最大值)
那么相对于每一个节点来说,第二个堆的最大值加次大值就是子树内经过这个节点的最长链
全局维护A堆,记录所有节点第二个堆的最大值和次大值的和。堆顶(最大值)就是答案
*/

//mn[i][j]表示j到j+2^i-1点的最小深度,ys是新编号,dep[i]第i号节点表示到1号节点的距离
int z,mn[20][210000],ys[110000],dep[110000];
void dfs(int x,int f)//得dfs序,为st表准备
{
    mn[0][++z]=dep[x];ys[x]=z;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(y!=f)
        {
            dep[y]=dep[x]+a[k].d;
            dfs(y,x);
            mn[0][++z]=dep[x];
        }
    }
}
/*
getrt
找到树的重心并存在G里。树的重心是一个点。
相比于其他点,以他为根的最大子树最小
*/
bool v[110000];//v表示这个点是否被访问过
//G是divi的那棵树的中心,sum是那棵树的总节点数
//tot[i]是以i为根的树节点数,g[i]是i为根的最大子树的节点数
int G,sum,tot[110000],g[110000];
void getrt(int x,int f)
{
    tot[x]=1;g[x]=0;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(y!=f&&v[y]==false)
        {
            getrt(y,x);
            tot[x]+=tot[y];
            g[x]=max(g[x],tot[y]);
        }
    }
    g[x]=max(g[x],sum-tot[x]);//还有到父亲这一条边的子树
    if(g[x]<g[G])G=x;
}
//按照新的树去建父子关系,当前这一层的中心去连接下一层的重心
int fa[110000];
void divi(int x)
{
    v[x]=true;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(v[y]==false)
        {
            sum=tot[y];G=0;
            getrt(y,x);
            fa[G]=x;divi(G);
        }
    }
}
//求x到y路径上深度最小的点的深度(最近公共祖先的深度 )
int rmq(int x,int y)
{
    x=ys[x];y=ys[y];
    if(x>y)swap(x,y);
    int t=Log[y-x+1];
    return min(mn[t][x],mn[t][y-Bin[t]+1]);
}
//求x到y之间的距离
int dis(int x,int y)
{
    return dep[x]+dep[y]-2*rmq(x,y);
}
void turn_black(int f,int x)
{
    if(f==x)
    {
        if(B[f].size()==1)
            A.push(B[f].top());
    }
    if(fa[f]==0)return ;
    int ff=fa[f],D=dis(ff,x),tmp=C[f].top();
    C[f].push(D);
    if(D>tmp)
    {
        int k1=B[ff].top()+B[ff].stop(),s1=B[ff].size();

        if(tmp!=0)B[ff].erase(tmp);
        B[ff].push(D);

        int k2=B[ff].top()+B[ff].stop(),s2=B[ff].size();
        if(k1<k2)
        {
            if(s1>=2)A.erase(k1);
            if(s2>=2)A.push(k2);
        }
    }
    turn_black(ff,x);
}
void turn_white(int f,int x)
{
    if(f==x)
    {
        if(B[f].size()==1)
            A.erase(B[f].top());
    }
    if(fa[f]==0)return ;
    int ff=fa[f],D=dis(ff,x),tmp=C[f].top();
    C[f].erase(D);
    if(D==tmp)
    {
        int k1=B[ff].top()+B[ff].stop(),s1=B[ff].size();

        B[ff].erase(D);
        if(C[f].top()!=0)B[ff].push(C[f].top());

        int k2=B[ff].top()+B[ff].stop(),s2=B[ff].size();
        if(k1>k2)
        {
            if(s1>=2)A.erase(k1);
            if(s2>=2)A.push(k2);
        }
    }
    turn_white(ff,x);
}
int cc;bool col[110000];
char ss[5];
int main()
{
    Bin[0]=1;for(int i=1;i<20;i++)Bin[i]=Bin[i-1]*2;
    Log[0]=-1;for(int i=1;i<=200000;i++)Log[i]=Log[i/2]+1;
    int n,m,x,y,c;
    scanf("%d",&n);
    len=0;memset(last,0,sizeof(last));
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&c);
        ins(x,y,c);ins(y,x,c);
    }
    dfs(1,0);
    //ST表
    for(int i=1;i<=Log[z];i++)
        for(int j=1;j<=z;j++)
            if(j+Bin[i]-1<=z)
                mn[i][j]=min(mn[i-1][j],mn[i-1][j+Bin[i-1]]);
    //j到j+2^i-1的最小值等于min(前半部分最小值,后半部分最小值)
    G=0;g[0]=2147483647;
    sum=n;getrt(1,0);
    fa[G]=0;divi(G);
    for(int i=1;i<=n;i++)
    {
        turn_black(i,i);
        col[i]=true;cc++;
    }
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",ss+1);
        if(ss[1]==‘A‘)
        {
            if(cc<=0)printf("They have disappeared.\n");
            else printf("%d\n",A.top());
        }
        else
        {
            scanf("%d",&x);
            if(col[x]==true){turn_white(x,x);cc--;}
            else            {turn_black(x,x);cc++;}
            col[x]=1-col[x];
        }
    }
    return 0;
}
时间: 2024-08-12 18:29:40

关于树论【动态点分治】的相关文章

[bzoj1095][ZJOI2007]Hide 捉迷藏 点分树,动态点分治

[bzoj1095][ZJOI2007]Hide 捉迷藏 2015年4月20日7,8876 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋子都互相可达.游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯.在起初的时候,所有的灯都没有被打开.每一次,孩子们只会躲藏在

震波——动态点分治+线段树

题目 [题目描述] 在一片土地上有 $N$ 个城市,通过 $N-1$ 条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为 $1$,其中第 $i$ 个城市的价值为 $value[i]$.不幸的是,这片土地常常发生地震,并且随着时代的发展,城市的价值也往往会发生变动. 接下来你需要在线处理 $M$ 次操作:- $0~x~k$ 表示发生了一次地震,震中城市为 $x$ ,影响范围为 $k$ ,所有与 $x$ 距离不超过 $k$ 的城市都将受到影响,该次地震造成的经济损失为所有受影响城市的价值和.-

BZOJ 3924: [Zjoi2015]幻想乡战略游戏(动态点分治)

这种动态点分治嘛,GDKOI时听打到了,也有同学讲到了,所以印象比较深刻也就想出来了,然后就在实现方面卡了好久= = 不得不说CLJ说得真的太简单了,实现方面根本没提. 首先我们可以先用树分治构建出这棵树的分治树,也就是把这棵树的重心作为根节点然后子树为他的子树的重心这样递归下去,然后每个节点存的是其子树的信息. 对于每个节点我们保存这个子树的dv的总和已经把该节点作为点的答案值 这样对于修改能在log n的时间内解决 寻找答案的时候,我们可以发现,如果现在节点的子树dv和*2大于总节点,那么向

bzoj1095: [ZJOI2007]Hide 捉迷藏 动态点分治学习

好迷啊...感觉动态点分治就是个玄学,蜜汁把树的深度缩到logn (静态)点分治大概是递归的时候分类讨论: 1.答案经过当前点,暴力(雾)算 2.答案不经过当前点,继续递归 由于原树可以长的奇形怪状(菊花啊..链啊..扫把啊..)这就导致各种方法都会被卡 于是通过每次找重心保证最大深度 动态怎么解决呢? 不妨考虑线段树是二分的固态版本(只可意会),那么我们把每次找到的重心固定下来长成一棵树就可以把点分治凝固(不可言传) 原来点分治该维护什么现在就维护什么... (事实上我并没有写过静态点分治..

【BZOJ1095】【ZJOI2007】捉迷藏 [动态点分治]

捉迷藏 Time Limit: 40 Sec  Memory Limit: 256 MB[Submit][Status][Discuss] Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏. 他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋子都互相可达. 游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯. 在起初

动态点分治

动态点分治 感觉动态点分治一直没有太懂呀. 一定是我太菜了 点分治还是很简单的: 每次找出当前树的重心 把树至少缩小一半 然后暴力把当前的子树上的所有的可能值全部算出来 只需要容斥的算一下重复的部分就行了 动态点分治 似乎代码就比点分治多了一行: 把点分治的树按照重心割开之后 只需要记录一下它在分治树的父亲是谁 分治树高保证是\(O(logn)\)级别的 (每次都把树的大小至少除了二,这是重心的性质) 这个时候的这棵树 虽然不能够直接当原树来搞 但是相当于每次都是一棵子树 点还是没有变的 所以,

【BZOJ1095】捉迷藏(动态点分治)

[BZOJ1095]捉迷藏(动态点分治) 题面 BZOJ 题解 动态点分治板子题 假设,不考虑动态点分治 我们来想怎么打暴力: \(O(n)DP\)求树的最长链 一定都会.不想解释了 所以,利用上面的思想 对于每个点,维护子树到他的最长链 以及子树到他的次长链 把这两个玩意拼起来就可能是答案啦 所以,每个点维护两个堆 一个维护子树上的点到他的距离 一个维护所有子树的前面那个堆的最大值 也就是所以子树中,到达当前点的最长链 再在全局维护一个堆 记录每个点最长链和次长链的和 这样子就可以动态的维护了

【BZOJ3730】震波(动态点分治)

[BZOJ3730]震波(动态点分治) 题面 BZOJ 题意 给定一棵树, 每次询问到一个点的距离\(<=K\)的点的权值之和 动态修改权值, 强制在线 题解 正常的\(DP\)??? 很简单呀. 每次暴力往父亲跳,不断的加值, 然后容斥一下就行了 现在要动态维护 就维护一下动态点分治 但是现在记录起来没那么容易了 于是开两棵线段树 每次做一下差 不断暴跳点分树的父亲就行啦 #include<iostream> #include<cstdio> #include<cst

动态点分治总结

动态点分治总结 其实也没有做很多道题,但是还是总结一波吧....... 要知道动态点分治,首先得知道点分治. 点分治就是对于一个联通块,求出这个联通块的重心,然后把这个联通块分为很多个联通块,这些联通块都为这个重心的子树,那么求出这些子树对于重心的贡献,然后递归地做下去,由于重心的每一棵子树大小都小于联通块的一半,那么可以达到快速求解的目的. 动态点分治,在点分治的基础上,利用分治的过程重新构建一颗点分树,那么每一次修改都只会影响到点分树上的父亲节点,重新计算对于这些节点的贡献即可,由于点分树的