bzoj3133 [Baltic2013]ballmachine

题目链接:bzoj3133
题目大意:
有一个装球机器,构造可以看作是一棵树。有下面两种操作:
1、从根放入一个球,只要下方有空位,球会沿着树滚下。如果同时有多个点可以走,那么会选择编号最小的节点所在路径的方向。
2、从某个位置拿走一个球,那么它上方的球会落下来。

对于每种操作op:
若op = 1,输出最后一个球落到了哪里
若op = 2,输出拿走那个球后有多少个球会掉下来

题解:
线段树
对于第一个操作,要求每次选择编号最小的节点所在路径的方向走,那么我们就可以知道它们优先顺序,即假设全为空的情况下小球下落的位置顺序。于是我们可以用一棵线段树来维护这个序列从左到右第一个为空的位置,即为从根放下一个球它会落到的位置。
又操作一就可以看成从根丢下一个球,重复很多次,那么答案就是最后一次球落到的位置。

对于操作二的话,拿走一个球后,影响的只是这个位置到根的路径,容易想到树剖。答案就是从这个位置到根的路径有多少个球,这个可以用深度直接求得,因为是链状的。拿走后上面的球会滚下来,于是就相当于拿走了这条路径上最靠近根的那个球。通过这个来维护状态。
再开一棵线段树按树链剖分的dfs序维护从左到右第一个有球的位置,即对于一条路径来说最靠近根的那个球的位置。

对于状态的改变,两棵线段树要一起维护。
于是那天,我花了一个下午种了两棵线段树

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
#define N 405010

struct node
{
    int x,y,next;
}a[N];int len,first[N],n,rt;
void insert(int x,int y)
{
    len++;a[len].x=x;a[len].y=y;
    a[len].next=first[x];first[x]=len;
}
int mp[N],rk[N],ss[N],id;
int mymin(int x,int y){return (x<y)?x:y;}
struct px{int x,id;}pt[N];
bool cmp(px x,px y) {return x.x<y.x;}
int st[N],ln[N],tot;
int siz[N],son[N],fa[N],top[N],dfn[N],fdfn[N],dep[N],cnt;
void dfs(int x)
{
    siz[x]=son[x]=0;
    if (ln[x]) {st[x]=tot;tot+=ln[x];}
    for (int k=first[x];k!=-1;k=a[k].next)
    {
        int y=a[k].y;
        dep[y]=dep[x]+1;fa[y]=x;dfs(y);
        if (siz[y]>siz[son[x]]) son[x]=y;
        siz[x]+=siz[y];
        mp[x]=mymin(mp[x],mp[y]);
    }
    siz[x]+=1;int tl=0;
    for (int k=first[x];k!=-1;k=a[k].next)
    {
        int y=a[k].y;
        px ls;ls.id=y;ls.x=mp[y];
        pt[st[x]+tl]=ls;tl++;
    }
    if (ln[x]) sort(pt+st[x],pt+st[x]+ln[x],cmp);
}
void get_rk(int x)
{
    if (ln[x])
    {
        for (int i=st[x];i<=st[x]+ln[x]-1;i++)
        {
            px now=pt[i];
            get_rk(now.id);
        }
    }rk[++id]=x;ss[x]=id;
}
void dfs2(int x,int tp)
{
    dfn[x]=++cnt;fdfn[cnt]=x;top[x]=tp;
    if (son[x]!=0) dfs2(son[x],tp);
    for (int k=first[x];k!=-1;k=a[k].next)
    {
        int y=a[k].y;
        if (y==fa[x] || y==son[x]) continue;
        dfs2(y,y);
    }
}
int p[N],c[N];
void bt(int now,int l,int r)//初始
{
    if (l==r) {p[now]=l;c[now]=0;return;}
    int mid=(l+r)>>1,lc=now*2,rc=now*2+1;
    bt(lc,l,mid);bt(rc,mid+1,r);
    c[now]=0;
    if (p[lc]!=0) p[now]=p[lc];
    else p[now]=p[rc];
}
int add(int now,int l,int r)//按顺序从左往右找位置放球
{
    if (l==r) {p[now]=0;return rk[l];}
    int ret,mid=(l+r)>>1,lc=now*2,rc=now*2+1;
    if (p[lc]) ret=add(lc,l,mid);
    else ret=add(rc,mid+1,r);
    if (p[lc]!=0) p[now]=p[lc];
    else p[now]=p[rc];
    return ret;
}
void del(int now,int l,int r,int x)//第x位的球被取走
{
    if (l==r) {p[now]=x;return;}
    int ret,mid=(l+r)>>1,lc=now*2,rc=now*2+1;
    if (x<=mid) del(lc,l,mid,x);
    else del(rc,mid+1,r,x);
    if (p[lc]!=0) p[now]=p[lc];
    else p[now]=p[rc];
}
void ins(int now,int l,int r,int x)
{
    if (l==r) {c[now]=x;return;}
    int mid=(l+r)>>1,lc=now*2,rc=now*2+1;
    if (x<=mid) ins(lc,l,mid,x);
    else ins(rc,mid+1,r,x);
    if (c[lc]!=0) c[now]=c[lc];
    else c[now]=c[rc];
}
void cel(int now,int l,int r,int x)
{
    if (l==r) {c[now]=0;return;}
    int mid=(l+r)>>1,lc=now*2,rc=now*2+1;
    if (x<=mid) cel(lc,l,mid,x);
    else cel(rc,mid+1,r,x);
    if (c[lc]!=0) c[now]=c[lc];
    else c[now]=c[rc];
}
int ffind(int now,int l,int r,int L,int R)
{
    if (l==L && r==R) return c[now];
    int mid=(l+r)>>1,lc=now*2,rc=now*2+1;
    if (R<=mid) return ffind(lc,l,mid,L,R);
    if (L>mid) return ffind(rc,mid+1,r,L,R);
    int ret=ffind(lc,l,mid,L,mid);
    if (ret==0) ret=ffind(rc,mid+1,r,mid+1,R);
    return ret;
}
int get_ans(int x)
{
    int la=0,ret=dep[x];
    while (x)
    {
        int ls=ffind(1,1,n,dfn[top[x]],dfn[x]);
        if (!ls && la) break;la=ls;
        x=fa[top[x]];
    }
    int d=fdfn[la];
    cel(1,1,n,dfn[d]);
    del(1,1,n,ss[d]);
    return ret-dep[d];
}
int main()
{
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
    int m,i,x,op;
    scanf("%d%d",&n,&m);
    len=0;memset(first,-1,sizeof(first));
    for (i=1;i<=n;i++) ln[i]=0;
    for (i=1;i<=n;i++)
    {
        scanf("%d",&x);
        if (x==0) rt=i;
        else insert(x,i);
        mp[i]=i;ln[x]++;
    }
    tot=1;id=cnt=0;dep[rt]=1;
    fa[rt]=0;dfs(rt);get_rk(rt);
    dfs2(rt,rt);bt(1,1,n);
    for (i=1;i<=m;i++)
    {
        scanf("%d%d",&op,&x);
        if (op==1)
        {
            int ans;
            while (x--) 
            {
                ans=add(1,1,n);
                ins(1,1,n,dfn[ans]);
            }
            printf("%d\n",ans);
        }else printf("%d\n",get_ans(x));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值