[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。



这题本来老师是让用动态点分治做的,但是我用了一种括号序列的做法。

我们可以将一个点在dfs便历时放入一个左括号,回溯的时候放入一个右括号,这样就得到了一棵树的括号序列。

这个括号序列有一个性质,可以通过这个括号序列得到树上两点的距离。

例如
这棵树的括号序列是:\((3(5(8))(4(2)(6))(1(7)))\)

假如要求2到7的距离,可以截取2到7这一段括号序列\(2)(6))(1(7\)

删去数字和可以匹配的括号,剩下:\())((\),一共四个括号,距离为4。

为什么有这个性质:

\(1^o\)只添加了左括号

这样子的话,这个点肯定是u的祖先,在路径上

\(2^o\)添加了左右括号

这样子的话,这个点肯定是祖先的其他儿子,或者是u点的儿子,产生不了贡献,所以要抵消。

\(3^o\)左右括号都没添加

这样子的话,这个点肯定是祖先的其他儿子,产生不了贡献,不用计算。

\(4^o\)只添加了右括号

这样子的话,有多少个右括号,就说明跳到lca需要多少步。

综述,上面的性质成立。

然后就到了维护答案的时候了。

设一个区间有\(a\)个右括号,\(b\)个左括号,左儿子区间有\(a1\)个右括号,\(b1\)个左括号,右儿子区间有\(a2\)个右括号,\(b2\)个左括号。

那这个大区间的答案就是\(a1+|b1-a2|+b2=max(a1+b1-a2+b2,a1-b1+a2+b2)=max((a1+b1)+(b2-a2),(a1-b1)+(a2+b2))\)。

显然\((a1+b1),(b2-a2),(a1-b1),(a2+b2)\)都是可以区间单独维护的,前缀维护l1(a+b),l2(b-a),r1(a+b),r2(a-b)。

然后就是区间合并答案了。

#### \(1^o\)(a,b)

\(\begin{cases}a=a1,b=b1-a2+b2&b1>a2\\a=a1+a2-b1,b=b2&b1\leqslant{a2}\end{cases}\)

这个很好理解吧。

\(2^o\)(l1,l2,b1,b2)

tree[hao].l1=max(tree[lc].l1,max(tree[rc].l1+tree[lc].a-tree[lc].b,tree[rc].l2+tree[lc].a+tree[lc].b));
tree[hao].l2=max(tree[lc].l2,tree[rc].l2-tree[lc].a+tree[lc].b);
tree[hao].r1=max(tree[rc].r1,max(tree[lc].r1-tree[rc].a+tree[rc].b,tree[lc].r2+tree[rc].a+tree[rc].b));
tree[hao].r2=max(tree[rc].r2,tree[lc].r2+tree[rc].a-tree[rc].b);

l1和l2是前缀的,所以整个区间要么是lson贡献的,要么是rson+lson贡献来的。

r1和r2同理。

\(3^o\)ans

tree[hao].sum=max(max(tree[lc].sum,tree[rc].sum),max(tree[lc].r1+tree[rc].l2,tree[lc].r2+tree[rc].l1));

直接按照推出来的式子计算即可

剩下的就是线段树基本操作了。

另:初始化时要\(-INF\)

#include<bits/stdc++.h>
#define lc hao<<1
#define rc hao<<1|1
#define inf 214748364
#define N 100010
using namespace std;
struct data
{
    int a,b,l1,l2,r1,r2,sum;//right left max(a+b) max(b-a) max(a+b) max(a-b) ans
}tree[N<<4];
int to[N<<1],nxt[N<<1],head[N],st[N<<2],nct,cnt,tot,val[N],n,m,x,y;
bool is[N];
char ch[2];
void adde(int x,int y)
{
    to[++nct]=y;
    nxt[nct]=head[x];
    head[x]=nct;
}
void dfs(int u,int fa)
{
    st[++cnt]=-1;
    st[++cnt]=u;
    val[u]=cnt;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v!=fa)
        {
            dfs(v,u);
        }
    }
    st[++cnt]=-2;
}
void up(int hao)
{
    if(tree[lc].b>tree[rc].a)
    {
        tree[hao].a=tree[lc].a;
        tree[hao].b=tree[lc].b-tree[rc].a+tree[rc].b;
    }else{
        tree[hao].b=tree[rc].b;
        tree[hao].a=tree[rc].a-tree[lc].b+tree[lc].a;
    }
    tree[hao].l1=max(tree[lc].l1,max(tree[rc].l1+tree[lc].a-tree[lc].b,tree[rc].l2+tree[lc].a+tree[lc].b));
    tree[hao].l2=max(tree[lc].l2,tree[rc].l2-tree[lc].a+tree[lc].b);
    tree[hao].r1=max(tree[rc].r1,max(tree[lc].r1-tree[rc].a+tree[rc].b,tree[lc].r2+tree[rc].a+tree[rc].b));
    tree[hao].r2=max(tree[rc].r2,tree[lc].r2+tree[rc].a-tree[rc].b);
    tree[hao].sum=max(max(tree[lc].sum,tree[rc].sum),max(tree[lc].r1+tree[rc].l2,tree[lc].r2+tree[rc].l1));
}
void change(int hao,int x)
{
    tree[hao].a=tree[hao].b=0;
    tree[hao].l1=tree[hao].r1=tree[hao].l2=tree[hao].r2=tree[hao].sum=-inf;
    if(st[x]==-1)
    {
        tree[hao].b=1;
    }else{
        if(st[x]==-2)
        {
            tree[hao].a=1;
        }else{
            if(!is[st[x]])
            {
                tree[hao].l1=tree[hao].r1=tree[hao].l2=tree[hao].r2=0;
            }
        }
    }
}
void build(int hao,int l,int r)
{
    if(l==r)
    {
        change(hao,l);
        return;
    }
    int mid=(l+r)>>1;
    build(lc,l,mid);
    build(rc,mid+1,r);
    up(hao);
}
void update(int hao,int l,int r,int x)
{
    if(l==r)
    {
        change(hao,l);
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)
    {
        update(lc,l,mid,x);
    }else{
        update(rc,mid+1,r,x);
    }
    up(hao);
}
int main()
{
//  freopen("1.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        adde(x,y);
        adde(y,x);
    }
    dfs(1,-1);
    tot=n;
    build(1,1,cnt);
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",ch);
        if(ch[0]=='G')
        {
            if(tot==1)
            {
                puts("0");
            }else{
                if(tot==0)
                {
                    puts("-1");
                }else{
                    printf("%d\n",tree[1].sum);
                }
            }
        }else{
            scanf("%d",&x);
            if(is[x])
            {
                tot++;
                is[x]=0;
            }else{
                tot--;
                is[x]=1;
            }
            update(1,1,cnt,val[x]);
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/2017gdgzoi44/p/12000768.html

时间: 2024-08-28 22:41:01

[ZJOI2007]捉迷藏(动态点分治)的相关文章

【BZOJ1095】[ZJOI2007]Hide 捉迷藏 动态树分治+堆

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

【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个屋子的灯. 在起初

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

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

BZOJ 1095 ZJOI2007 Hide 捉迷藏 动态树分治+堆

题目大意:给定一棵树,一开始每个点都是黑点,多次改变某个点的状态或询问距离最远的两个黑点的距离 <珍爱生命远离STL可是我还是可耻地用了STL系列> 传说中的动态树分治...其实并没有那么神嘛= = ↑别听这傻瓜瞎说这货被STL卡了一天QAQ 我们把分治过程中遍历过的重心都连起来 上一层的重心链接下一层的重心 可以得到一棵新的树 下面我们开始讨论这棵新树 显然这棵树的高度不会超过O(logn) 然后我们每个节点开两个堆 第一个堆记录子树中所有节点到父亲节点的距离 第二个堆记录所有子节点的堆顶

bzoj 1095 [ZJOI2007]Hide 捉迷藏 动态点分治+堆

题面 题目传送门 解法 挺恶心的题 考虑动态点分治,先建出点分树 然后每一个点开两个堆,分别为\(a,b\) \(a_i\)表示点分树上\(i\)子树中所有节点在原树上和点分树中\(i\)父亲的距离,\(b_i\)表示点分树中\(i\)所有儿子的堆顶 再开一个堆\(ans\),存每一个\(b_i\)最大和次大值的和 在修改的时候,分两种情况考虑 但是本质都是一样的,就是在点分树上不断爬,爬到根为止,然后对当前点和父亲的\(a,b\)进行删除和加入 细节比较多,需要注意 重点:传入一个结构体参数的

bzoj 1095 Hide 捉迷藏 - 动态点分治 -堆

捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋子都互相可达.游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯.在起初的时候,所有的灯都没有被打开.每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯.为了评估某一次游戏的复杂

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

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

[ZJOI2007]捉迷藏 解题报告 (动态点分治)

[ZJOI2007]捉迷藏 近期做过的码量最大的一题 (当然也是我写丑了....) 题意 有一个 \(n\) 个节点的树 (\(n \le 10^5\)), 每个节点为黑色或白色. 有 \(m\) 个操作 (\(m \le 5 \times 10^5\)), 操作有两种, 将点 \(x\) 的的颜色翻转. 查询树上距离最远的黑色点对之间的距离. 思路 首先, 如果没有修改操作的话, 就是一个裸的点分治 (点分治学习笔记). 有修改操作, 那就 动态点分治. 动态点分治的基本思路是 (个人总结的)

动态点分治入门 ZJOI2007 捉迷藏

传送门 这道题好神奇啊--如果要是不带修改的话那就是普通的点分治了,每次维护子树中距离次大值和最大值去更新. 不过这题要修改,而且还改500000次,总不能每改一次都点分治一次吧. 所以我们来认识一个新东西:带修改的点分治,动态点分治! 它可以强势解决带修改点分治问题(但是这玩意真的太难了我这个菜鸡只能学到入门) 首先我们要建立一棵树(点分树),这棵树是由点分治每次所分治的所有子树的重心串起来的.为什么要这么做呢?因为对于每次的修改,其实并没有影响到特别多的结果,它只会影响它自己所在的子树的重心