[Luogu3960][NOIP2017]列队

题解

真是吉利题。。。

考虑到每一行除了最后一个列的人之外除非出队否则都不会离开这一行,而在一些人出队后又会有其他的人填充进来

我们将前一部分的人用树状数组维护,而后一部分的人用树状数组加vector维护

对于每个询问,如果询问不在最后一列,则根据给出的出队时的位置,处理出人在这一行出现的人中累计的位置(就是假设人不出队)

我们假设那些填充进来的人都已经进入了本行,初始让每一个人的值都为1

给出一个人出队时的编号,即为查询该行中前缀和等于给出的y值的位置,先记录这个位置,并将这个位置的值改成0

当然会发现这个位置是可能大于等于m的,就是之后填充进来的数,我们现在不知道这些人是谁,也不用管他们是谁而单纯的把位置记录下来

那怎么维护呢?

用树状数组维护这些信息,用二分找出前缀和等于一个特定的数的位置,时间复杂度 l o g 2 m log^2m log2m

如果每一行都开一个树状数组,显然空间承受不住,我们离线处理,对每一行开一个vector,将每个非最后一列的查询push_back进这一行的vector中,枚举每一行共用一个树状数组进行处理(当然一行用完是需要让树状数组变成原来的初始状态的)

接下来对于我们按顺序枚举每个操作

对每一行和最后一列都开一个vector记录出队后又回来的人 (和上边的不是一个vector)

最后一列可以看作一行,当和最后一列中一个人同排的人出队时,最后一列的这个人需要向左移,当他自己出队时,他需要push_back进最后一列的vector中,都相当于从这一中出队,最后一列的数除非相当于出队,否则都不会离开这一列,这样想,就可以像上边处理行那样处理了,也是假设所有人已经填充进来

我们像对上边的行一样对最后一列使用一个树状数组,无论y是几,先找出给定的x值原本的位置,将这个数“出队”(这里的出队也可能是去了这一行除了最后一列的其他位置)

如果该操作的y是这一列的最后一个人 那么他这个位置对应的数就是答案,把这个位置的值改成0,并把这个位置对应的数视为又回来的数push_back进最后一列的vector

当然,这个位置可能是大于n的,这个位置对应的数就是最后一列第 位置-n 个回来的数,即从vector中找答案

否则那个“出队”的最后一列的人就进入了第x行,将他对应的数push_back进这一行回来的人的vector

现在的之前记录的位置对应的数如果小于m直接计算答案,如果大于等于m,就从vector中找答案

如果vector中不够呢?

vector中肯定是够的,因为这一行的人出队时,总会有人补进来从而被push_backvector

3 e 5 × 3 e 5 3e5 \times 3e5 3e5×3e5大于 INT_MAX(2147483647)得开 long long(不然你就会变成55分)

代码

#include <cstdio>
#include <vector>
#include <algorithm>
#define lowbit(x) ((x)&(-(x)))
using namespace std;
const int MAXN=300010;
int bit[MAXN<<1];
int totn,n,m,q;
void add(int x) {
    for(;x<=totn;x+=lowbit(x)) ++bit[x];
}
void remove(int x) {
    for(;x<=totn;x+=lowbit(x)) --bit[x];
}
int query(int x) {
    int ans=0;
    for(;x;x-=lowbit(x)) ans+=bit[x];
    return ans;
}
int find_kth(int k) {
    int l=1,r=totn;
    while(l+1<r) {
        int mid=(l+r)>>1;
        if(query(mid)>=k) r=mid;
        else l=mid; 
    }
    if(query(l)==k) return l;
    else return r;
}
void init() {
    for(int i=1;i<=totn;++i) add(i);
}
struct QuesT {
    int x,y;
}qt[MAXN];
vector<long long>bg[MAXN];  //记录每行除最后一列外的查询
vector<long long>hl[MAXN];  //记录后来进来的人               0为最后一列,其他为对应的行数
int rec[MAXN];
int main() {
    scanf("%d%d%d",&n,&m,&q);
    totn=max(n,m)+q;
    for(int i=1;i<=q;++i) {
        scanf("%d%d",&qt[i].x,&qt[i].y);
        if(qt[i].y!=m) {    //如果是最后一位则不一定在该行中,可能会在最后一列的其他地方
            bg[qt[i].x].push_back(i);
        }
    }
    init();   //树状数组初始化把每个都变成1
    for(int i=1;i<=n;++i) {
        for(vector<long long>::size_type j=0;j<bg[i].size();++j) {
            rec[bg[i][j]]=find_kth(qt[bg[i][j]].y);
            remove(rec[bg[i][j]]);
        }
        for(vector<long long>::size_type j=0;j<bg[i].size();++j) {     //memset就炸了
            add(rec[bg[i][j]]);
        }
    }
    for(int i=1;i<=q;++i) {
        int wh=find_kth(qt[i].x);
        remove(wh);
        long long ans=wh>n?hl[0][wh-n-1]:(long long)wh*m;    //减 1 因为 vector 从 0 开始
        if(qt[i].y!=m) {
            hl[qt[i].x].push_back(ans);
            ans=rec[i]>=m?hl[qt[i].x][rec[i]-m]:(long long)(qt[i].x-1)*m+rec[i];
        } 
        hl[0].push_back(ans);
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值