洛谷P3168 [CQOI2015]任务查询系统:(主席树 + 差分 + 对主席树前缀和的理解)

题目描述
最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分。超级计算机中的任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行),其优先级为Pi。同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同。调度系统会经常向查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个)的优先级之和是多少。特别的,如果Ki大于第Xi秒正在运行的任务总数,则直接回答第Xi秒正在运行的任务优先级之和。上述所有参数均为整数,时间的范围在1到n之间(包含1和n)。

这道神仙题,倒不是很难,卡了我两个晚上,合起来写了8个小时,刷新了我的三观和对主席树。。树状数组的认知。

Si,Ei 小于1e5,很容易想到以时间顺序建主席树(我称它为第一维),但是每一项任务的有效时间一个区间,也很容易想到差分。想得到差分就想得到树状数组。于是开始了我第一晚的树套树。

第一晚为什么写炸了呢?因为我对差分的理解错了,对主席树建树的第一维差分,把我弄糊涂了。。
主席树是自带前缀和的,为什么呢?因为第一维每个位置的树继承自上一个位置,然后就更新了自己这个点的数据,它拥有前面所维护的所有数据,以及这一点自己的数据,这和求前缀和的过程是一样的。

第一晚炸了是因为我还把第i 棵树 这当成了 前i分钟所包含的所有的任务,当然如果没加差分的话,这是对的。但是如果维护的是差分,第i 棵树其实是第i分钟 所有的任务(差分啊,前缀和变成了这个点的信息)。如果是一维的差分数组,怕是很容易想明白,而到了二维偏序的主席树上,就跟魔术一样,前后像维护差分一样各搞一个更新,就真的变成了差分。。(废话)

第 i 棵树维护的信息是前缀和仍然是对的, 但这是个时候维护的是前i分钟的修改量了(差分数组),因此前缀和等于第i分钟的任务数。

今晚终于想通了这个,结果树套树T了,而且疯狂RE和WA,罢了懒得调了,看到T了就知道没戏了。
既然主席树自带前缀和,哪用得着树状数组来维护差分?(第一发就是想用树状数组维护差分)

不需要树状数组维护差分,可以直接线性维护,但这个时候必须把需要维护的任务按时间排序,因为时间最大才1e5,我们可以建1e5棵树,第i棵树代表前i分钟的修改量的前缀和,整个过程并不是先差分再求前缀和的过程(不能像数组那样搞,先差分再求和)。因为主席树的前缀和来自于对前面的树的继承,必须先继承再更新,如果先更新再继承会丢失信息(仔细想一想),因为我们的继承只是简单的令root[i] = root[i - 1]而已。。。并不是很智能。

所以整个过程变成了一边差分,一边求和。所以要讲任务按时间排序,到第i分钟时维护所有第i分钟需要维护的信息。

然后这题还一个坑点。。:原做法是先找出第ki个数离散化后是几,再在这个区间内找区间和,因此要维护两个信息。问题如果离散化进行了去重,前ki个任务里可能多有个优先级相同的任务,去重后主席树查询到的位置可能包括不止ki个任务。
因此离散化不能去重(被迫使用其他离散化方式)
然后就可以A掉这题(写困了两个晚上)

PS:一开始写了一个超级SB的查询。。。把查询分成了两步,其实一步就可以了。。。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
struct ss{
	int ls,rs,sum;
	long long val;
}tree[maxn * 100];
struct tt{
	int id;
	int p,k,v;
}a[maxn * 4];

struct ll{
	int id;
	int v;
}t[maxn * 3];
int root[maxn * 10],sz,p,tot;
int rank[maxn * 3];
bool cmp(tt a,tt b){
	if(a.p == b.p) return a.v > b.v;
	return a.p < b.p;
}
bool cmp2(ll a,ll b){
	return a.v < b.v;
}
void init(){
	root[0]= 0;
	tree[0] = {0,0,0,0};
	sz = tot = 0; 
}
void update(int &rt,int l,int r,int k,long long ki,long long v){
	tree[++sz] = tree[rt];
	rt = sz;
	tree[rt].sum += v;
	if(l == r){
		tree[rt].val += ki * v;
		return ;
	}
	int mid = l + r >> 1;
	if(mid >= k) update(tree[rt].ls,l,mid,k,ki,v);
	else update(tree[rt].rs,mid + 1,r,k,ki,v);
	tree[rt].val = tree[tree[rt].rs].val + tree[tree[rt].ls].val;
}
long long query(int rt,int l,int r,int k){
	if(l == r) return tree[rt].val;
	int tmp = tree[tree[rt].ls].sum;
	int mid = l + r >> 1;
	if(tmp >= k) return query(tree[rt].ls,l,mid,k);
	else return query(tree[rt].rs,mid + 1,r,k - tmp) + tree[tree[rt].ls].val;
}
int n,m;
int main(){
	scanf("%d%d",&n,&m);
	int len = 0,cur = 1;
	int si,ei,ki;
	for(int i = 1; i <= n; i++){
		scanf("%d%d%d",&si,&ei,&ki);
		t[++tot] = {i,ki};
		a[++len] = {i,si,ki,1};
		a[++len] = {i,ei + 1,ki,-1};
	}
	sort(a + 1,a + len + 1,cmp);
	sort(t + 1,t + tot + 1,cmp2);
	for(int i = 1; i <= tot; i++) rank[t[i].id] = i;
	for(int i = 1; i <= maxn; i++){
		root[i] = root[i - 1];
		while(a[cur].p == i && cur <= len){
			update(root[i],1,tot,rank[a[cur].id],a[cur].k,a[cur].v);
			cur++;
		}
	}
	long long pre = 1;
	long long x,ai,bi,ci;
	for(int i = 1; i <= m; i++){
		scanf("%lld%lld%lld%lld",&x,&ai,&bi,&ci);
		long long ki = 1 + (ai * pre + bi) % ci;
		pre = query(root[x],1,tot,ki);
		printf("%lld\n",pre);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值