bzoj 1095: [ZJOI2007]Hide 捉迷藏 (动态点分治)

题目描述

传送门

题目大意:给出一棵树,树上的每个点有黑白两种状态,求树上最远两个黑点的距离。
会改变点的状态。

题解

这道题如果不会改变点的状态,那么就是一道点分治的裸题。因为点的状态会动态的改变,所以就引进了一种新的算法——动态点分治。
首先回顾一下点分治的过程,点分治其实就是每次找到重心,然后处理与重心有关的路径。时间复杂度的之所以能够保证是因为如果按照每次找出的重心建树,那么树高不会超过 logn ,那么对于一个点来说,最多被统计 logn 次,如果统计是 O(1) ,那么均摊的时间复杂度就是 nlogn
动态点分治就相当于用线段树处理序列问题一样,在点分治的基础上加上对于每个点关键信息的维护,一般是加入一个数据结构,实现快速的查询和修改。

动态点分治的实现过程

以这道题为例。。。
(1)建立重心树
在点分治中真正用了更新答案的实际是重心上的路径信息,所以对于每个点来说我们只需要维护其作为重心时的有关信息即可。我们先根据找到的重心建立一棵重心树,一个点作为重心时隔开后得到的若干棵子树中的每个重心的父重心就是这个点。根据点分治的性质,一个点的改变只会影响这个点作为重心时重心树上的所有祖先节点,因为重心树的树高是 logn 的,所以每次修改一个点,最多只需要修改 logn 个节点维护的信息。
重心树可以不真的建出来,只需要记录一下每个点的父重心即可。
(2)构建可删堆(或者某种数据结构)
如果是静态的对于每个点我们只需要知道他作为重心时经过他的两个黑点形成的最长链,如果是动态的话,点状态的改变会影响最长链的选取,所以我们需要将这些黑点到重心的信息记录下来,并维护最大值。对于这道题来说,堆其实是一个不错的选择。
堆的话,一般可以用STL中的优先队列来代替(优点:空间用多少算多少),但是优先队列有一个弊端就是不支持删除任意点,所以我们考虑怎么利用两个堆实现动态删除的效果。
对于每个维护最大值的堆,再开一个删除堆,删除一个节点的时候我们将要删除的值放到删除堆中,每次取top的时候如果两个堆的堆顶元素是一样的就同时pop,知道两个堆的top不同或者删除堆为空,此时堆的堆顶就是真正的堆顶。
(3)维护信息
对于每个点维护两个堆,第一个堆C维护这个重心的子树中的点到父重心的距离
(注意是这个重心的父重心!)。第二个堆B维护这个重心的所有子重心中黑点的最大深度(也就是所有子重心的C堆堆顶)。最后用一个堆A维护所有重心B堆的最大值和次大值的和,A的堆顶就是答案,注意如果某个点是黑点,那么这个点的B堆中还要加入一个距离0,确保这个点和子树中的黑点可以形成合法的路径并更新答案。
(4)修改操作
把白点变黑点和黑点变白点分开考虑。先从C堆中更改,如果C堆的变化影响到了B堆,那么就修改B堆,B的变化影响到了A就修改A。反正是按照优先级的顺序修改即可。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define N 200003
#define inf 1000000000
using namespace std;
int tot,point[N],v[N],nxt[N],belong[N],fa[N][20],mi[20];
int size[N],dep[N],f[N],sum,pd[N],Light,vis[N],root,n,m;
void add(int x,int y)
{
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; 
    tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
struct heap{
    priority_queue<int> h,d;
    void push(int x){ h.push(x);}
    void del(int x){ d.push(x);}
    void pop(){
        while (!d.empty()&&h.top()==d.top()) 
         h.pop(),d.pop();
        h.pop();
    }
    int top(){
        while (!d.empty()&&h.top()==d.top())
         h.pop(),d.pop();
        if (!h.size()) return 0;
        return h.top();
    }
    int secondtop() {
        if (size()<2) return 0;
        int t=top(); pop();
        int t1=top(); push(t);
        return t1;
    }
    int size() {
        return h.size()-d.size();
    }
}a,b[N],c[N];
void dfs(int x,int father) {
    dep[x]=dep[father]+1;
    for (int i=1;i<=19;i++) {
        if (dep[x]-mi[i]<0) break;
        fa[x][i]=fa[fa[x][i-1]][i-1];
    }
    for (int i=point[x];i;i=nxt[i]){
        if (v[i]==father) continue;
        fa[v[i]][0]=x;
        dfs(v[i],x);
    }
}
int lca(int x,int y){
    if (dep[x]<dep[y]) swap(x,y);
    int k=dep[x]-dep[y];
    for (int i=0;i<=19;i++)
     if ((k>>i)&1) x=fa[x][i];
    if (x==y) return x;
    for (int i=19;i>=0;i--)
     if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int dis(int x,int y) {
    return dep[x]+dep[y]-2*dep[lca(x,y)];
}
void getroot(int x,int father)
{
    size[x]=1; f[x]=0;
    for (int i=point[x];i;i=nxt[i]){
        if (v[i]==father||vis[v[i]]) continue;
        getroot(v[i],x);
        size[x]+=size[v[i]];
        f[x]=max(f[x],size[v[i]]);
    }
    f[x]=max(f[x],sum-size[x]);
    if (f[x]<f[root]) root=x;
}
void divi(int x,int father)
{
    belong[x]=father; vis[x]=1;
    for (int i=point[x];i;i=nxt[i]){
        if (vis[v[i]]) continue;
        sum=size[v[i]]; root=0;
        getroot(v[i],x);
        divi(root,x);
    }
}
void turnoff(int u,int v)
{
    if (u==v) {
        b[u].push(0);
        if (b[u].size()==2) a.push(b[u].top());
    }
    if (!belong[u]) return;
    int f=belong[u],D=dis(f,v),tmp=c[u].top();
    c[u].push(D);
    if (D>tmp) {
        int mx=b[f].top()+b[f].secondtop(),size=b[f].size();
        if (tmp) b[f].del(tmp);
        b[f].push(D);
        int now=b[f].top()+b[f].secondtop();
        if (now>mx) {
            if (size>=2) a.del(mx);
            if (b[f].size()>=2) a.push(now);
        }
    }
    turnoff(f,v);
}
void turnon(int u,int v)
{
    if (u==v) {
        if (b[u].size()==2) a.del(b[u].top());
        b[u].del(0);
    }
    if (!belong[u]) return;
    int f=belong[u],D=dis(f,v),tmp=c[u].top();
    c[u].del(D);
    if (D==tmp) {
        int mx=b[f].top()+b[f].secondtop(),size=b[f].size();
        b[f].del(D);
        if (c[u].top()) b[f].push(c[u].top());
        int now=b[f].top()+b[f].secondtop();
        if (now<mx) {
            if (size>=2) a.del(mx);
            if (b[f].size()>=2) a.push(now);
        }
    }
    turnon(f,v);
}
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d",&n); Light=n;
    for (int i=1;i<=n;i++) pd[i]=1;
    for (int i=1;i<n;i++) {
        int x,y; scanf("%d%d",&x,&y);
        add(x,y);
    }
    dfs(1,0); mi[0]=1;
    for (int i=1;i<=20;i++) mi[i]=mi[i-1]*2;
    root=0; f[0]=inf; sum=n;
    getroot(1,0); divi(root,0);
    for (int i=1;i<=n;i++) c[i].push(0);
    for (int i=1;i<=n;i++) turnoff(i,i);
    scanf("%d",&m);
    while (m--) {
        char s[10]; scanf("%s",s+1);
        if (s[1]=='G') {
            if (Light<=1) printf("%d\n",Light-1);
            else printf("%d\n",a.top()); 
        }
        if (s[1]=='C') {
            int x; scanf("%d",&x);
            if (pd[x]) turnon(x,x),tot--;
            else turnoff(x,x),tot++;
            pd[x]^=1;
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值