bzoj1095:Hide 捉迷藏(动态树分治)

我月考时一直在想动态树分治是个什么东西,直到看了这题题解才有点懂。

题面
题意:给出一棵黑白树,每次翻转一个点的颜色,或询问两个黑点间的最远距离。

由于是点对问题,可以想点分治。先考虑问题的静态版本,怎么用点分治求最远两个黑点的距离。
显然,对于一个重心,我们要知道每个连通块中,距离它最远的点的距离,前二大的加起来可以更新答案。

根据点分治的划分过程,每个重心向它的次级重心连边,形成了一棵点分树。显然点分树的深度不超过logN。这就意味着,给每个点开个数据结构,存储其点分树子树的所有点,每个点的信息只出现了logN次,总空间不超过NlogN。

然后考虑这个问题的动态版本,由于每个点的信息只出现了logN次,我们就暴力更新这logN个信息。根据本题要维护的信息,我们可以用三个大根堆实现。

每个重心维护一个堆C,存储其块内所有点到它父重心的路径长。
每个重心维护一个堆B,表示它每个连通块中到它的最长路径,即是每个子树堆C的堆顶。
在用一个全局堆A统计答案,即所有堆B中的前二大的和。

有一种改进STL优先队列使其可以删除某个元素的方法:
多开一个优先队列存储已经删除的元素,求堆顶时若两个堆堆顶相同就一起把堆顶弹出。

据说还有一种线段树维护括号序列的方法。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))

typedef long long LL;

const int N=200200,oo=1e9;

void read(int &hy)
{
    hy=0;
    char cc=getchar();
    while(cc<'0'||cc>'9')
    cc=getchar();
    while(cc>='0'&&cc<='9')
    {
        hy=(hy<<3)+(hy<<1)+cc-48;
        cc=getchar();
    }
}

struct yy
{
    priority_queue<int> R,D;
    void push(int x) { R.push(x);}
    void pop(int x) { D.push(x);}
    int top()
    {
        while(!D.empty() && R.top()==D.top())
        R.pop(),D.pop();

        return R.top();
    }
    int top2()
    {
        int tmp=top(),ans;
        pop(tmp);
        ans=tmp+top();
        push(tmp);
        if(ans==tmp||tmp==0)
        return 0;
        return ans;
    }
}A,B[N],C[N];

int to[N],nex[N],head[N],cnt;
int n,m,num,col[N];
bool vis[N];
int dep[N],f[N],pre[N][18],p[N],siz[N],g[N],tim[N],times,sum,root;

void add(int u,int v)
{
    to[++cnt]=v;
    nex[cnt]=head[u];
    head[u]=cnt;
}

void dfs(int x,int fa)
{
    if(!dep[x])
    dep[x]=dep[fa]+1;

    f[x]=fa;

    if(!pre[x][0])
    pre[x][0]=fa;

    if(!tim[x])
    tim[x]=++times;

    g[x]=0;
    siz[x]=1;

    for(int h=head[x];h;h=nex[h])
    if(to[h]!=fa&&!vis[to[h]])
    {
        dfs(to[h],x);
        siz[x]+=siz[to[h]];
        g[x]=max(g[x],siz[to[h]]);
    }
    g[x]=max(g[x],sum-siz[x]);
    if(g[x]x;
}

int dis(int x,int y)
{
    if(x==y)
    return 0;

    if(tim[x]>tim[y])
    swap(x,y);
    int hy=y;
    for(int i=17;i>=0;i--)
    if(tim[pre[y][i]]>tim[x])
    y=pre[y][i];
    return dep[x]+dep[hy]-2*dep[pre[y][0]];
}

void dfs2(int x)
{
    siz[f[x]]=sum-siz[x];
    vis[x]=1;
    for(int h=head[x];h;h=nex[h])
    if(!vis[to[h]])
    {
        sum=siz[to[h]];
        root=0;
        dfs(to[h],to[h]);
        p[root]=x;
        dfs2(root);
    }
}

void update(int x)
{
    A.pop(B[x].top2());
    if(!col[x])
    B[x].push(1);
    else
    B[x].pop(1);
    A.push(B[x].top2());

    for(int now=x;p[now];now=p[now])
    {
        int len=dis(x,p[now])+1;

        A.pop(B[p[now]].top2());
        B[p[now]].pop(C[now].top());
        if(!col[x])
        C[now].push(len);
        else
        C[now].pop(len);

        B[p[now]].push(C[now].top());

        A.push(B[p[now]].top2());

    }
    num-=col[x];
    col[x]=!col[x];
    num+=col[x];
}

int main()
{
    cin>>n;
    for(int i=1;iint u,v;
        read(u);
        read(v);
        add(u,v);
        add(v,u);
    }

    g[0]=oo;
    sum=n;
    dfs(1,1);

    dfs2(root);

    for(int j=1;j<=17;j++)
    for(int i=1;i<=n;i++)
    pre[i][j]=pre[pre[i][j-1]][j-1];

    for(int i=1;i<=n;i++)
    {
        B[i].push(0),B[i].push(0);
        C[i].push(0),C[i].push(0);
        A.push(0);
    }

    for(int i=1;i<=n;i++)
    update(i);

    cin>>m;
    while(m--)
    {
        char cc=getchar();
        while(cc!='G'&&cc!='C')
        cc=getchar();

        if(cc=='G')
        {
            if(num<=1)
            printf("%d\n",num-1);
            else
            printf("%d\n",A.top()-2);
        }
        else
        {
            int x;
            read(x);
            update(x);
        }
    }
    return 0;
}

你可能感兴趣的:(树分治)