#线段树#洛谷 3988 [SHOI2013]发牌

传送门


分析

fake:此题不就是链表模拟题吗,我一开始还真这么想

貌似链表什么用都没有,根据题意很清楚,要找一个支持删除和查询区间第\(k\)大的数据结构

解释一下为什么题目可以转换为查询区间第\(k\)大,

如果记录一下上一次删除的位置(这里指的是排名)和总牌数,是不是可以从\(上一次删除位置+kth\)对总牌数取模以重新找到下一次查询的排名,这样周而复始

那这个数据结构要找什么呢平衡树

平衡树当然可以,但是我目前只敲过模板,所以还是算了

也可以用线段树,维护区间未被删除的数的个数,查询第\(k\)大时,如果左区间未被删除的数的个数不小于在当前区间的排名,那么往左边走,否则往右边走

时间复杂度是\(O(n \log n)\)。(当然可以用树状数组+倍增解决,时间复杂度相同)

然而跑得比多带一个log的树状数组+二分还要慢


代码

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
int w[2800011],n;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
void print(int ans){
    if (ans>9) print(ans/10);
    putchar(ans%10+48);
}
inline void build(int k,int l,int r){
    if (l==r) {w[k]=1; return;}
    rr int mid=(l+r)>>1;
    build(k<<1,l,mid),build(k<<1|1,mid+1,r);
    w[k]=w[k<<1]+w[k<<1|1];
}
inline void update(int k,int l,int r,int x){
    if (l==r) {w[k]=0; return;}
    rr int mid=(l+r)>>1;
    if (x<=mid) update(k<<1,l,mid,x);
        else update(k<<1|1,mid+1,r,x);
    w[k]=w[k<<1]+w[k<<1|1];
}
inline signed kth(int k,int l,int r,int x){
    if (l==r) return l;
    rr int mid=(l+r)>>1;
    if (w[k<<1]>=x) return kth(k<<1,l,mid,x);
        else return kth(k<<1|1,mid+1,r,x-w[k<<1]);
}
signed main(){
    n=iut(),build(1,1,n);
    for (rr int i=1,now=1;i<=n;++i,putchar(10)){
        rr int t=(iut()+now)%w[1],del; if (!t) t=w[1];
        print(del=kth(1,1,n,now=t)),update(1,1,n,del);
    }
    return 0;
}
posted @ 2020-02-19 16:03  lemondinosaur  阅读(96)  评论(8编辑  收藏  举报