【BZOJ1095】捉迷藏(ZJOI2007)-动态点分治+堆

测试地址:捉迷藏
做法:本题需要用到动态点分治+堆。
一棵树上有黑点和白点,求两个黑点之间的最远距离,如果没有修改,我们完全可以使用DP或者点分治的方法求出。现在的问题是带了修改,那么从DP的角度来考虑就比较难了,所以我们从点分治的角度考虑怎么拓展。
回顾点分治的思路,我们对一棵树找到它的重心,然后递归处理删去这个点后该树分成的所有子树。对于每一棵树,考虑过重心的所有路径,用类似DP的方法可以求出这棵树内过重心的最远距离黑点对。考虑这样一种暴力,每次修改都暴力做一次点分治,这样显然会增大时间复杂度。但考虑到,每次修改只涉及到一个点,也就是说只涉及到一端为这个点的所有路径,我们发现它最多会影响 logn log ⁡ n 个点上求出的最远点对。至于为什么,请继续往下看。
考虑构造这样一个结构来描述点分治的过程:对于一棵树,先将它的重心作为根,递归处理它的所有子树,并从重心向这些子树的重心分别连边。显然这样连出来的也是一棵树,这棵树的深度最多为 logn log ⁡ n 。我们把这棵树称为点分树,它是一个描述点分治过程的结构。
我们发现,修改一个点,影响到的点就是它在点分树上的所有祖先,也就是最多影响到 logn log ⁡ n 个。回顾原先的点分治需要维护什么东西:对于一棵点分树上的子树,维护子树内所有黑点到根的祖先的最远距离;对于点分树上的每一个点,维护几棵子树中最远黑点距离的最大值和次大值;当然,还要维护对于所有的点,求出的最远黑点点对的距离。我们发现这些都是要维护某些值中最大的几个值,用堆来维护即可,至于可定值删除堆的写法详见本人的代码。那么我们每修改一个点,都暴力修改它在点分树上所有祖先的信息,每次修改一个祖先的信息都是 O(logn) O ( log ⁡ n ) 的,于是我们就得到了一个时间复杂度为 O(mlog2n) O ( m log 2 ⁡ n ) ,空间复杂度为 O(mlogn) O ( m log ⁡ n ) 的做法,可以通过此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,first[100010]={0},firstt[100010]={0},tot=0,dep[100010]={0},fa[100010];
int f[100010],mxson[100010],siz[100010],q[100010],totsiz,top;
int dis[100010][20],totvis;
bool vis[100010]={0};
struct edge
{
    int v,next;
}e[200010],t[100010];
struct heap
{
    priority_queue<int> a,b;
    void push(int x) {if (x>=0) a.push(x);}
    void del(int x) {if (x>=0) b.push(x);}
    int size() {return a.size()-b.size();}
    int top()
    {
        if (!size()) return -1;
        while(!b.empty()&&a.top()==b.top()) a.pop(),b.pop();
        return a.top();
    }
    int sum()
    {
        if (size()<2) return -1;
        int A=top();a.pop();
        int B=top();push(A);
        return A+B;
    }
}p[100010],st[100010],ans;
void insert(int a,int b) {e[++tot].v=b,e[tot].next=first[a],first[a]=tot;}
void insertt(int a,int b) {t[++tot].v=b,t[tot].next=firstt[a],firstt[a]=tot;}

void dp(int v,int fa)
{
    totsiz++;
    q[++top]=v;
    mxson[v]=0;
    siz[v]=1;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=fa&&!vis[e[i].v])
        {
            dp(e[i].v,v);
            mxson[v]=max(mxson[v],siz[e[i].v]);
            siz[v]+=siz[e[i].v];
        }
}

int find(int v)
{
    totsiz=top=0;
    dp(v,0);
    int ans=100000000,ansi;
    for(int i=1;i<=top;i++)
        if (max(mxson[q[i]],totsiz-siz[q[i]])<ans)
        {
            ans=max(mxson[q[i]],totsiz-siz[q[i]]);
            ansi=q[i];
        }
    return ansi;
}

void put(int v,int tar,int d,int Fa)
{
    dis[v][dep[v]-dep[tar]]=d;
    p[tar].push(d);
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=Fa&&!vis[e[i].v]) put(e[i].v,tar,d+1,v);
}

int solve(int v,int Fa)
{
    v=find(v);
    vis[v]=1;
    dep[v]=dep[Fa]+1;
    fa[v]=Fa;
    for(int i=first[v];i;i=e[i].next)
        if (!vis[e[i].v])
        {
            int nx=solve(e[i].v,v);
            insertt(v,nx);
            put(e[i].v,nx,1,0);
        }

    st[v].push(0);
    for(int i=firstt[v];i;i=t[i].next)
        st[v].push(p[t[i].v].top());
    ans.push(st[v].sum());

    vis[v]=0;
    return v;
}

void turn(int v)
{
    vis[v]=!vis[v];
    if (vis[v])
    {
        totvis--;
        ans.del(st[v].sum());
        st[v].del(0);
        ans.push(st[v].sum());
    }
    else
    {
        totvis++;
        ans.del(st[v].sum());
        st[v].push(0);
        ans.push(st[v].sum());
    }

    int h=0,x=v;
    while(x)
    {
        if (fa[x])
        {
            ans.del(st[fa[x]].sum());
            st[fa[x]].del(p[x].top());
        }
        if (vis[v]) p[x].del(dis[v][h]);
        else p[x].push(dis[v][h]);
        if (fa[x])
        {
            st[fa[x]].push(p[x].top());
            ans.push(st[fa[x]].sum());
        }
        h++;
        x=fa[x];
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        insert(a,b),insert(b,a);
    }

    tot=0;
    solve(1,0);
    totvis=n;

    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        char op[3];
        scanf("%s",op);
        if (op[0]=='G')
        {
            if (totvis>=2) printf("%d\n",ans.top());
            else printf("%d\n",totvis?0:-1);
        }
        else
        {
            int x;
            scanf("%d",&x);
            turn(x);
        }
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值