NOIP2017_D2T3 列队

题目略(各大OJ上都有)

题解:

我们可以观察到一个规律,就是每次移动都只会改变一行和最后一列(最后一列是关键),于是我们就可以把这个方阵划分成这样

每个划分的区间只需要快速支持这两个操作:

  1. 从中查询一个位置的值并删除
  2. 向末尾添加一个值

因为最多只有q个添加操作,q有 3 × 1 0 5 3 \times 10^5 3×105而一个序列原本就有 3 × 1 0 5 3 \times 10^5 3×105这么长,用高级数据结构显然太浪费,动态开点的线段树就可以搞定,两个操作一次时间复杂度都是 l o g ( n + q ) log (n+q) log(n+q),新开的空间也是 l o g ( n + q ) log (n+q) log(n+q),而一次查询2个或4个操作就可以搞定。

总时间复杂度: O ( n l o g ( n + q ) ) O( nlog(n+q) ) O(nlog(n+q)),空间同

那么这个动态开点线段树怎么写呢:

把区间总范围设为 1 − ( m a x ( n , m ) + q ) 1-(max(n,m)+q) 1(max(n,m)+q),每个节点都设一个值cal表示节点区间包含的有效节点个数,叶节点再加一个val表示值,在这里,删除是指把此位置标为无效节点,并没有从序列中抹除,而2操作则是直接把最后一个有效点后的位置更新成要添加的值。

删除时直接在线段树上做二分,把叶节点cal改成0,上面的路径更新一下即可。

设一个Xi数组表示第i个序列的Xi位置还没有被插入过,每次插入时就可以精准定位,查完后Xi++,虽然不能保证和当前最后一个有效点紧挨着,但一定不会超出范围,这样效果是一样的。

下图展示了样例的第一个操作的过程:

有一步最为关键:如果一开始一个一个地更新初始方阵的每一个值,空间时间都要炸飞。可是不更新的话,前n或m-1个位置的值都不同,后q个位置又都是无效位置,没法操作。这时我们可以这样想,一个节点没有被建立,说明这个区间范围内没有被更新过,而我们知道节点所在序列,具体区间范围,又知道初始状态,那么我们就可以直接计算出有关这个节点的所有数据,而如果节点已被建立,直接使用节点数据即可。

还有就是此题卡常严重,非树状数组的童鞋们最好还是小心一点~~

代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N=3e5+5;
struct Node{
    Node* l;
    Node* r;
    int cal;
    long long val;
    Node(){
        cal=val=0;
        l=r=NULL;
    }
};
int n,m,q,Len,Xi[N];
Node* Root[N];

Node* NewNode(){
    return new Node();
}

inline int Cal(Node* o,int l,int r,int Limit)
{
    if(o!=NULL)
        return o->cal;
    r=min(Limit,r);
    return (l>r?0:r-l+1);
}

void update(Node* o,int l,int r,int k,long long val0,int Limit)
{
    o->cal++;
    if(l==r){
        o->val=val0;
        return;
    }
    int mid=(l+r)>>1,l_cal=Cal(o->l,l,mid,Limit);
    if(k<=mid)
    {
        if(o->l==NULL){
            o->l=NewNode();
            o->l->cal=l_cal;
        }
        update(o->l,l,mid,k,val0,Limit);
    }
    else
    {
        if(o->r==NULL){
            o->r=NewNode();
            o->r->cal=o->cal-1-l_cal;
        }
        update(o->r,mid+1,r,k,val0,Limit);
    }
}

long long query(Node* o,int l,int r,int k,int Limit,int id)
{
    o->cal--;
    if(l==r){
        if(!o->val)
        {
            long long id0=id,m0=m,l0=l;
            o->val=(id0?((id0-1ll)*m0+l0):(l0*m0));
        }
        return o->val;
    }
    int mid=(l+r)>>1,l_cal=Cal(o->l,l,mid,Limit);	
    if(k<=l_cal)
    {
        if(o->l==NULL){
            o->l=NewNode();
            o->l->cal=l_cal;
        }
        return query(o->l,l,mid,k,Limit,id);
    }
    else
    {
        if(o->r==NULL){
            o->r=NewNode();
            o->r->cal=o->cal+1-l_cal;
        }
        return query(o->r,mid+1,r,k-l_cal,Limit,id);
    }
}

int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=0;i<=n;i++)
    {
        Root[i]=NewNode();
        Root[i]->cal=m-1;
        Xi[i]=m;
    }
    Root[0]->cal=n;
    Xi[0]=n+1;
    Len=max(n,m)+q;	
    while(q--)
    {
        int x,y;
        long long ans;
        scanf("%d%d",&x,&y);
        ans=query(Root[0],1,Len,x,n,0);
        if(y!=m)
        {
            long long ans1=query(Root[x],1,Len,y,m-1,x);
            update(Root[x],1,Len,Xi[x],ans,m-1);
            Xi[x]++;
            ans=ans1;
        }
        printf("%lld\n",ans);
        update(Root[0],1,Len,Xi[0],ans,n);
        Xi[0]++;
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值