洛谷 P2056 [ZJOI2007]捉迷藏 动态树分治

题目描述

Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋子都互相可达。

游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。

我们将以如下形式定义每一种操作:

C(hange) i 改变第i个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。
G(ame) 开始一次游戏,查询最远的两个关灯房间的距离。
输入输出格式

输入格式:
第一行包含一个整数N,表示房间的个数,房间将被编号为1,2,3…N的整数。

接下来N-1行每行两个整数a, b,表示房间a与房间b之间有一条走廊相连。

接下来一行包含一个整数Q,表示操作次数。接着Q行,每行一个操作,如上文所示。

输出格式:
对于每一个操作Game,输出一个非负整数到hide.out,表示最远的两个关灯房间的距离。若只有一个房间是关着灯的,输出0;若所有房间的灯都开着,输出-1。

输入输出样例

输入样例#1:
8
1 2
2 3
3 4
3 5
3 6
6 7
6 8
7
G
C 1
G
C 2
G
C 1
G
输出样例#1:
4
3
3
4
说明

对于 20 % 20\% 20% 的数据, N ≤ 50 , M ≤ 100 N ≤50, M ≤100 N50,M100
对于 60 % 60\% 60% 的数据, N ≤ 3000 , M ≤ 10000 N ≤3000, M ≤10000 N3000,M10000
对于 100 % 100\% 100% 的数据, N ≤ 100000 , M ≤ 500000 N ≤100000, M ≤500000 N100000,M500000

分析:
动态树分治模板题。
我们把某个分治中心与他分开的连通块的分治中心连边,这样就有一棵分治树,可以知道树高为 l o g n logn logn
我们对于每个点的每个儿子维护一个堆(set也可以),那么所有儿子的堆顶最大值和次大值就是答案(维护儿子堆顶也要开一个堆)。再给所有的点维护一个堆,表示每个分治中心的答案。
至于求两点距离,可以使用括号序,然后求两点的最小值。可以用rmq解决。
这样预处理就是 O ( n l o g n ) O(nlogn) O(nlogn)的,查询距离是 O ( l o g n ) O(logn) O(logn)的,因为每次修改要查询 O ( l o g n ) O(logn) O(logn)个距离。而由于每个堆并不是满的,所以修改总操作应该是 O ( l o g n ) O(logn) O(logn)的(这个可以像多项式求逆那样分析)。总复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)的,但是常数比较大。

代码:

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;

const int N=100005;

int n,m,fa[N],dep[N],pos[N],rmq[N*2][20],cnt,dfn,last[N],lg[N*2],size[N],mx[N],tot,root;
bool vis[N],clo[N];
struct edge{int to,next;}e[N*2];

struct Heap
{
    priority_queue<int> 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 top()
    {
        while (b.size()&&a.top()==b.top()) a.pop(),b.pop();
        if (!a.size()) return 0;
        else return a.top();
    }

    int size()
    {
        return a.size()-b.size();
    }

    int stop()
    {
        if (size()<2) return 0;
        int x=top();pop();
        int y=top();push(x);
        return y;
    }
}a,b[N],c[N];

void addedge(int u,int v)
{
    e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
    e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;
}

void dfs(int x,int fa)
{
    dep[x]=dep[fa]+1;pos[x]=++dfn;rmq[dfn][0]=dep[x];
    for (int i=last[x];i;i=e[i].next)
    {
        if (e[i].to==fa) continue;
        dfs(e[i].to,x);
        rmq[++dfn][0]=dep[x];
    }
}

int get_dis(int x,int y)
{
    int l=pos[x],r=pos[y];
    if (l>r) swap(l,r);
    int len=lg[r-l+1],mn=min(rmq[l][len],rmq[r-(1<<len)+1][len]);
    return dep[x]+dep[y]-2*mn;
}

void get_root(int x,int fa)
{
    size[x]=1;mx[x]=0;
    for (int i=last[x];i;i=e[i].next)
    {
        if (vis[e[i].to]||e[i].to==fa) continue;
        get_root(e[i].to,x);
        size[x]+=size[e[i].to];
        mx[x]=max(mx[x],size[e[i].to]);
    }
    mx[x]=max(mx[x],tot-size[x]);
    if (!root||mx[x]<mx[root]) root=x;
}

void build(int x,int y)
{
    vis[x]=1;fa[x]=y;
    for (int i=last[x];i;i=e[i].next)
    {
        if (vis[e[i].to]) continue;
        root=0;tot=size[e[i].to];
        get_root(e[i].to,0);
        build(root,x);
    }
}

void turn_off(int u,int v)
{
    if (u==v)
    {
        c[u].push(0);
        if (c[u].size()==2) a.push(c[u].top());
    }
    if (!fa[u]) return;
    int f=fa[u],d=get_dis(v,f),tmp=b[u].top();
    b[u].push(d);
    if (d>tmp)
    {
        int mx=c[f].top()+c[f].stop(),size=c[f].size();
        if (tmp) c[f].erase(tmp);
        c[f].push(d);
        int now=c[f].top()+c[f].stop();
        if (now>mx)
        {
            if (size>=2) a.erase(mx);
            if (c[f].size()>=2) a.push(now);
        }
    }
    turn_off(f,v);
}

void turn_on(int u,int v)
{
    if (u==v)
    {
        c[u].erase(0);
        if (c[u].size()==1) a.erase(c[u].top());
    }
    if (!fa[u]) return;
    int f=fa[u],d=get_dis(v,f),tmp=b[u].top();
    b[u].erase(d);
    if (d==tmp)
    {
        int mx=c[f].top()+c[f].stop(),size=c[f].size();
        c[f].erase(d);
        if (b[u].size()) c[f].push(b[u].top());
        int now=c[f].top()+c[f].stop();
        if (now<mx)
        {
            if (size>=2) a.erase(mx);
            if (c[f].size()>=2) a.push(now);
        }
    }
    turn_on(f,v);
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        addedge(x,y);
    }
    dfs(1,0);
    for (int i=1;i<=dfn;i++) lg[i]=log(i)/log(2);
    for (int j=1;j<=lg[dfn];j++)
        for (int i=1;i<=dfn-(1<<j)+1;i++)
            rmq[i][j]=min(rmq[i][j-1],rmq[i+(1<<(j-1))][j-1]);
    root=0;tot=n;
    get_root(1,0);
    build(root,0);
    for (int i=1;i<=n;i++) clo[i]=1,turn_off(i,i);
    scanf("%d",&m);
    while (m--)
    {
        char ch[2];
        scanf("%s",ch);
        if (ch[0]=='C')
        {
            int x;
            scanf("%d",&x);
            if (clo[x]) clo[x]=0,turn_on(x,x);
            else clo[x]=1,turn_off(x,x);
        }
        else printf("%d\n",a.top());
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值