BZOJ 3932 [CQOI2015] 任务查询系统 可持久化线段树

题目大意:有n个任务持续m秒,每个任务从si秒开始到ei秒结束且有一个优先级pi,有m个询问,回答第xi秒时正在执行的任务中优先级前k小和,强制在线。

根据时间的变化,任务会开始或结束。我们想知道任一时刻任务的执行情况,这里我们就需要用到可持久化数据结构。对于这道题,我们可以维护一个权值线段树1

可持久化数据结构,暴力地想就是每个版本完全复制一个保存起来,但是空间不够用。
如果每次只是修改一个点的话,在线段树中会有logn个点的信息发生变化,其余仍然和前一秒一样,这样的话直接复用前一秒的版本,空间复杂度从n^2变为nlogn.

具体如何实现呢?
先在第0秒创建一个空版本。把每一个任务分成两个操作:在第si秒插入在ei+1秒删除。把操作按照时间排序然后构建每一秒的权值线段树。插入或删除时要新建结点。

权值范围太大这里需要离散化。
在复制结点的时候不要忘记复制儿子指针。
k要开long long。
线段树查找第k大时递归到点树上要进行讨论。
改了一上午。离散化辣眼睛凑合看吧0.0
UPD 2017/7/1 更新了可读的代码

#include <cstdio>
#include <algorithm>
#include <queue>
#define N 100005
#define INF 1e7
using namespace std;
typedef long long LL;
int n,m,top,b[N];
LL ans=1;
struct Node {
    Node *ch[2];
    int siz;
    LL sum;
    Node() {}
    Node(Node* tmp) {
        if(!tmp) siz=0 , sum=0 , ch[0]=ch[1]=NULL;
        else siz=tmp->siz , sum=tmp->sum , ch[0]=tmp->ch[0] , ch[1]=tmp->ch[1];
    }
    void* operator new(size_t) {
        static Node *mempool,*C;
        if(mempool==C) mempool=(C=new Node[1<<20])+(1<<20);
        return C++;
    }
}*root[N];
void Init(Node*& o,int l,int r) {
    o=new Node(NULL);
    if(l==r) return ;
    int mid=l+r>>1;
    Init(o->ch[0],l,mid); Init(o->ch[1],mid+1,r);
    return ;
}
void Insert(Node*& o,int v,int l,int r) {
    Node* tmp=o;
    o=new Node(tmp);
    int flag=(v>0?1:-1);
    o->siz+=flag , o->sum+=flag*b[abs(v)];
    if(l==r) return ;
    int mid=l+r>>1;
    if(abs(v)<=mid) Insert(o->ch[0],v,l,mid);
    else Insert(o->ch[1],v,mid+1,r);
    return ;
}
LL Query(Node* o,int k,int l,int r) {
    if(k>=o->siz) return o->sum;
    if(l==r) return (o->sum/o->siz)*k;
    int mid=l+r>>1;
    if(o->ch[0]) {
        if(o->ch[0]->siz>=k) return Query(o->ch[0],k,l,mid);
        return o->ch[0]->sum+Query(o->ch[1],k-o->ch[0]->siz,mid+1,r);
    }
    return Query(o->ch[1],k,mid+1,r);
}
struct Operation {
    int ord,val;
    Operation(int x=0,int y=0):ord(x),val(y){}
    bool operator < (const Operation& rhs) const { return ord<rhs.ord; }
    void adjust() {
        int flag=val>0?1:-1;
        val=lower_bound(b+1,b+top+1,abs(val))-b;
        val*=flag;
        return ;
    }
}p[N*2];
int main() {
    scanf("%d%d",&n,&m);
    Init(root[0],1,n);
    for(int i=1,l,r,v;i<=n;i++) {
        scanf("%d%d%d",&l,&r,&v);
        p[i]=Operation(l,v);
        p[i+n]=Operation(r+1,-v);
        b[++top]=v;
    }
    sort(b+1,b+top+1);
    top=unique(b+1,b+top+1)-b-1;
    for(int i=1;i<=2*n;i++) p[i].adjust();
    sort(p+1,p+2*n+1);
    LL k=1;
    for(int i=1;i<=m;i++) {
        root[i]=root[i-1];
        while(p[k].ord==i && k<=n*2) Insert(root[i],p[k++].val,1,n);
    }
    for(int i=1;i<=m;i++) {
        int x,a,b,c;
        scanf("%d%d%d%d",&x,&a,&b,&c);
        k=1+(a*ans+b)%c;
        printf("%lld\n",ans=Query(root[x],k,1,n));
    }
    return 0;
}

  1. 权值线段树是啥?就是结点编号不再代表序列编号,而是权值大小,常在每个结点处维护size域表示当前结点代表的权值范围内有几个数,用于查询第k小。
    第k小怎么查?和Treap差不多啦
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值