题目大意:有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;
}
- 权值线段树是啥?就是结点编号不再代表序列编号,而是权值大小,常在每个结点处维护size域表示当前结点代表的权值范围内有几个数,用于查询第k小。
第k小怎么查?和Treap差不多啦 ↩