洛谷P3960 [NOIP 2017 提高组] 列队 分析与思考

这道题独特在需要自己设计一个能够完成特定操作的数据结构而不是直接套用现有的数据结构

每次将一个数取出,若这个数的坐标是(x,y),这个数右边的数要全部往左挪一个,最后一列,第x行以后的数要全部向上挪一个

这样挪来挪去,往后考虑就不太好有思路了

这样划分这个数的阵列:

C(R0)
R112345
R2678910
R31112131415
R41617181920

其中R表示row行,C表示column列

对于R1,R2,.....,Rn,每次把一个数(这个数位于上图中的蓝色部分,也就是第1到m-1列),挪走,可以看作在Rx(x是挪走的数的行坐标)那行中,把这个数删去,删去这个数后,R0中第x行的数补到第Rx的末尾

发现,C同样也是这样,每次操作会删去第x个数(x是数的横坐标),然后将被删去的那个数补到末尾

将C看作第0行,即R0

因此,需要一种数据结构,可以删除每行中的第i个数(人为指定i),然后将一个数添加到该行末尾

要高效实现这个数据结构,可以使用树状数组,快速求一个序列的前缀和

什么序列呢?以R1为例 1 2 3 4,初始的时候所有数都存在,这个序列是 1 1 1 1 

如果删掉第3个数 序列变为 1 1 0 1,再删掉第二个数 ,变为 1 0 0 1,因此1-i的前缀和为1-i中有多少个数还在序列中,写成pref[i],每次要修改第y列的数,(也就是R1中存在的第y个数)就是求满足pref[i]=y的最小i值(因为有0所以可能有多个i满足要求),这一点使用二分实现

增加数的时候,每行使用一个vector记录该行末尾新增加的数

但是,每行使用一个树状数组,内存不够用,所以可以针对第1-n行,记录每一行删去的数的纵坐标的集合y1,y2....,(y1表示第删去当前的第y1个数)和每个y对应的操作序号id1,id2...,预处理每一行每次操作删去的数为[包含删去和没有删去的数的序列]的第几个数,针对每一行记录下来

比如 1 2 3 4 5,被删成 1 4 5以后,要删第二个数(此时y=2),此时pref[4]=2并且4是使pref[i]=2的最小i,所以删的是实际上的第4个数

在最后对每个操作进行回答的时候,用树状数组维护R0的前缀和

复杂度分析

若每行序列极限长度为mx,则mx=max(m,n)+q

预处理的复杂度:mxlog(mx)+q*(log(mx)+log(mx)*log(mx)) 

计算结果时的复杂度:q*(log(mx)+log(mx)*log(mx))

综合来看,为mxlog(mx)+q*(log(mx)+log(mx)*log(mx)) 

mx和q同一数量级,所以是qlog(q)+q(logq+logqlogq),综合一下是q(logq)^2

若q取到 3*1e5,大约是47715265<100000000=1e8

#include<iostream>
#include<cstdio>
#include<utility>
#include<vector>
using namespace std;

#define lowbit(x) (x&(-x))
#define ll long long

const int maxn=3*1e5+5;
int tree[maxn*2],pre[maxn],optx[maxn],opty[maxn];
int n,m,q,mx;

vector<pair<int,int>> row_op[maxn];
vector<ll> add[maxn];

void update(int x,int d){
    while(x<=mx){
        tree[x]+=d;
        x+=lowbit(x);
    }
}

int query(int x){
    int res=0;
    while(x){
        res+=tree[x];
        x-=lowbit(x);
    }
    return res;
}

int bin_search(int tar){
    int l=1,r=mx+1;
    int ans=-1;
    while(l<r){
        int mid=(l+r)>>1;
        if(query(mid)>=tar){
            ans=mid;
            r=mid;
        }else{
            l=mid+1;
        }
    }
    return ans;
}

int main()
{
    freopen("D:\\in.txt","r",stdin);
    ios::sync_with_stdio(0);cin.tie(0);

    cin>>n>>m>>q;
    mx=max(m,n)+q;
    //由于内存限制,只能一行一行地预处理每次删去的数的原位置
    //所以保存每行删去的数的位置,以及对应的操作序号
    for(int i=1;i<=q;i++){
        int x,y;cin>>x>>y;
        optx[i]=x;opty[i]=y;
        if(y!=m){
            row_op[x].push_back({y,i});
        }
    }
    //预处理出行删去的数的原位置
    //树状数组初始化为每个数都存在
    for(int i=1;i<=mx;i++){
        update(i,1);
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<row_op[i].size();j++){
            pair<int,int> t=row_op[i][j];
            int y=t.first,id=t.second;

            int ori=bin_search(y);
            pre[id]=ori;
            update(ori,-1);
        }
        for(int j=0;j<row_op[i].size();j++){
            pair<int,int> t=row_op[i][j];
            int y=t.first,id=t.second;

            update(pre[id],1);
        }
    }
    //1.维护最后一列,维护新增的数 2.输出结果
    for(int i=1;i<=q;i++){
        int x=optx[i],y=opty[i];
        //将最后一列的第x个数,加入第x行的新增数的序列中
        int ori=bin_search(x);
        update(ori,-1);
        //输出结果
        ll ans;
        ll last;
        if(ori<=n){
            last=1LL*ori*m;
        }else{
            last=add[0][ori-n-1];
        }
        if(y<m){        //针对行的询问
            if(pre[i]<m){
                ans=pre[i]+1LL*(x-1)*m;
            }else{
                ans=add[x][pre[i]-m];
            }
        }
        else {
            ans=last;
        }
        cout<<ans<<"\n";
        //更新add
        add[0].push_back(ans);
        if(y<m){
            add[x].push_back(last);
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值