Ants on tree

Posted Rolling...yoyoball!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ants on tree相关的知识,希望对你有一定的参考价值。

Time Limit: 1000 ms Memory Limit: 256 MB

Description

  从前有一个策略游戏, 叫做 蚂蚁上树
  游戏中有一棵 \(n\) 个节点, 以 1 为根的有根树
  初始始每个节点都为空, 游戏系统会进行两种操作 :
  1 x , 表示往 \(x\) 节点放入一只睡眠状态中的蚂蚁
  2 x , 表示从 \(x\) 节点取出一只睡眠状态中的蚂蚁
  (对于操作2, 保证取出前该点至少有一只蚂蚁)
  每次操作后, 玩家要进行一轮游戏 :
  游戏有无穷的时间, 每一时刻, 系统会 依次执行 下述五个操作
  1) 让玩家选择 任意多只(可以为 0 只) 睡眠状态中的蚂蚁
  2) 所有亢奋状态的蚂蚁朝根结点方向移动一步
  3) 若某一时刻 ≥2≥2只 亢奋状态 的蚂蚁处在同一节点, 游戏失败
  4) 到达根节点的蚂蚁进入睡眠状态.
  5) 当前时刻被玩家选择的蚂蚁进入亢奋状态
  6) 若所有蚂蚁都在根节点, 游戏结束
  游戏不允许失败, 玩家的游戏目的是 : 使游戏结束时, 最后一只到达根节点的蚂蚁到达时间最早.
  每轮游戏后, 系统会自动将树恢复成玩家该轮游戏前的局面, 然后进行下一次取/放蚂蚁的操作.

Input

  第一行两个数 \(n,m\) 表示树的点数和操作数
  第 \(2?n\) 行, 第 \(i\) 行一个数 \(f_i\) 表示 \(i\) 节点的父亲
  接下来\(m\) 行, 每行两个数表示系统的操作
  若为 1 x , 表示往 \(x\) 节点放入一只睡眠状态中的蚂蚁
  若为 2 x , 表示从 \(x\) 节点取出一只睡眠状态中的蚂蚁

Output

  输出 mm 行, 表示每轮游戏在最优策略下
  最后一只到达根节点的蚂蚁到达的最早时间
  (特别的, 如果所有蚂蚁都在根节点, 或者没有蚂蚁, 输出 0)

Sample Input

4 5
1
2
2
1 1
1 3
1 4
1 2
2 3

Sample Output

0
3
4
4
3

HINT

  对于样例输出第四行的解释 :
  第一时刻触碰位于 2, 3 的那只蚂蚁, 他们进入亢奋状态但没有移动
  第二时刻触碰位于 4 的那只蚂蚁, 然后位于 2, 3 的蚂蚁分别爬到 1, 2, 然后爬到 1 的蚂蚁进入睡眠状态, 之后 4 进入亢奋状态.
  第三时刻不触碰蚂蚁, 当前位于 2, 4 的蚂蚁分别爬到 1, 2, 爬到 1 的这只蚂蚁进入睡眠状态
  第四时刻不触碰蚂蚁, 当前位于 2 的蚂蚁爬到 1 并进入睡眠状态, 然后游戏结束
  数据范围 :
  对于 30%的数据,$ n,m≤3000$
  对于另外 30% 的数据, \(n≤5000\)
  对于另外 5%的数据, 树的最大深度为 \(2\)
  对于另外 10%的数据, 数据的生成方式如下 \(f_i=\)rand()%(i?1)+1
  对于 100%的数据 :\(2≤n≤10^5\)\(1≤m≤10^5\)\(1≤f_i<i,i=2..n\)\(1≤x≤n\)


Solution

  • 30%

  好像有一种线段树暴力维护的方法?mnlogn的应该是

  • 100%

  依旧是一种用线段树爆搞的方法

  (然而其实还有一种很优秀的做法是用平衡树来维护然而我太菜了不会qwq)

  首先分析一下问题,要求不能有两只亢奋状态的蚂蚁撞在一起,而蚂蚁的“行进速度”又是一样的,稍微思考一下会发现,其实只要两只蚂蚁到达终点(也就是根节点)的时间不同,这两只蚂蚁肯定不会撞上

  所以现在的问题就变成了,要给每只蚂蚁分配一个到达终点的时间,并且这个时间要大于或者等于这只蚂蚁所在节点的深度
  
?

  然后就考虑怎么维护了,我们考虑以到达根节点的时间为区间种一棵线段树,最底层的节点维护能在大于等于这个时间点到达根节点的蚂蚁的数量,这里有一种十分神秘的维护方式(然而还是被用平衡树的dalao们D飞了qwq):

  对于一只蚂蚁,我们只要给它分配一个大于等于其节点深度的到达时间就好了,也就是说对于一只在节点\(x\)的蚂蚁来说,到达时间在\([dep[x],maxdep]\)这段区间都是ok的,所以这只蚂蚁的到达时间应该是这段区间内第一个为\(0\)的时间点(为啥是第一个?因为要求总时间最少嘛)

  然而实际上,我们并不用明确每只蚂蚁具体在什么时间到达,我们只要知道每个时间区间可以有多少只蚂蚁选择就好了,因为其实谁先谁后并不重要,照这样的思路,插入操作就变成了给线段树的\(dep[x]\)\([dep[x],maxdep]\)中第一个为\(0\)的时间点这段区间加上\(1\),说明这只蚂蚁可以选在这段区间中的某个时间点到达根节点

  这样的思考方式就为删除提供了便利

  对于删除,因为每只蚂蚁这样看来都是一样的,所以为了让答案更优,我们只要找到\([dep[x],maxdep]\)中最后的那个为\(1\)的时间点(记为\(loc\) )然后把这个时间点的蚂蚁删掉就好了,具体实现起来就是给\([dep[x],loc]\)这个区间减去1,说明能在这段区间达到根节点的蚂蚁少了一只

  对于答案的统计,就是找到最后一个可能有蚂蚁选择的位置\(x\),然后最终的\(ans\)就是\(x\)加上可能选择大于等于这个时间点的蚂蚁的数量

  具体实现也是比较简单的,对于每个区间维护一个最大值和一个最小值爆搞即可

  

  代码大概长这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
#define lch ch[x][0]
#define rch ch[x][1]
using namespace std;
const int MAXN=1e5+10,SEG=MAXN*8;
int rt[MAXN];
namespace Seg{/*{{{*/
    int ch[SEG][2],sz[SEG],tag[SEG],mn[SEG],mx[SEG];
    int tot,n;
    void _build(int x,int l,int r){
        sz[x]=0;
        if (l==r) return;
        int mid=l+r>>1;
        lch=++tot; _build(lch,l,mid);
        rch=++tot; _build(rch,mid+1,r);
    }
    void pushup(int x){
        mn[x]=min(mn[lch],mn[rch]);
        mx[x]=max(mx[lch],mx[rch]);
    }
    void givetag(int x,int delta){mn[x]+=delta; mx[x]+=delta; tag[x]+=delta;}
    void downtag(int x){
        if (!tag[x]) return;
        if (lch) givetag(lch,tag[x]);
        if (rch) givetag(rch,tag[x]);
        tag[x]=0;
    }
    void build(int _n){tot=1;n=_n;_build(1,1,n);}
    int newnode(){
        ch[++tot][0]=ch[tot][1]=sz[tot]=0;
        return tot;
    }
    void _update(int x,int l,int r,int lx,int rx,int delta){
        if (l<=lx&&rx<=r){
            givetag(x,delta);
            return;
        }
        downtag(x);
        int mid=lx+rx>>1;
        if (l<=mid) _update(lch,l,r,lx,mid,delta);
        if (r>mid) _update(rch,l,r,mid+1,rx,delta);
        pushup(x);
    }
    void update(int l,int r,int delta){_update(1,l,r,1,n,delta);}
    int _find(int x,int d,int lx,int rx,int delta){
        if (mn[x]>delta) return rx+1;
        if (lx==rx) return lx;
        downtag(x);
        int mid=lx+rx>>1,ret=0;
        if (d<=mid) ret=_find(lch,d,lx,mid,delta);
        if (d>mid||ret==mid+1) ret=_find(rch,d,mid+1,rx,delta);
        return ret;
    }
    int find(int d,int delta){return _find(1,d,1,n,delta);}
    int _query(int x,int lx,int rx){
        if (!mx[x]) return 0;
        if (lx==rx) return lx+mx[x];
        downtag(x);
        int mid=lx+rx>>1;
        if (mx[rch]) return _query(rch,mid+1,rx);
        return _query(lch,lx,mid);
    }
    int query(){return _query(1,1,n);}
};/*}}}*/
struct xxx{
    int y,nxt;
}a[MAXN*2];
int h[MAXN],dep[MAXN],f[MAXN],cnt[MAXN],tm[MAXN];
int n,m,tot,all,ans,mxdep;
void add(int x,int y);
void dfs(int fa,int x,int d);

int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
#endif
    int x,y,op;
    scanf("%d%d",&n,&m);
    bool flag=true;
    memset(h,-1,sizeof(h));
    tot=0;
    for (int i=2;i<=n;++i){
        scanf("%d",f+i);
        add(f[i],i);
    }
    Seg::build(n*4);
    ans=0;mxdep=0;
    dfs(0,1,0);
    int tmp;
    for (int i=1;i<=m;++i){
        scanf("%d%d",&op,&x);
        if (op==1){
            if (x!=1){
                tmp=Seg::find(dep[x],0);
                Seg::update(dep[x],min(tmp,mxdep),1);
            }
        }
        else{
            if (x!=1){
                tmp=Seg::find(dep[x],1);
                Seg::update(dep[x],min(tmp,mxdep),-1);
            }
        }
        ans=Seg::query();
        printf("%d\n",ans);
    }
}

void add(int x,int y){
    a[++tot].y=y; a[tot].nxt=h[x]; h[x]=tot;
}

void dfs(int fa,int x,int d){
    int u; dep[x]=d; mxdep=max(dep[x],mxdep);
    for (int i=h[x];i!=-1;i=a[i].nxt){
        u=a[i].y;
        if (u==fa) continue;
        dfs(x,u,d+1);
    }
}

以上是关于Ants on tree的主要内容,如果未能解决你的问题,请参考以下文章

cmake更新版本简记

常用的几个JQuery代码片段

dsu on tree(无讲解)

CF 600E. Lomsat gelral(dsu on tree)

QTREE - Query on a tree

「luogu2633」Count on a tree