题目描述
最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分。超级计算机中的任务用三元组(Si,Ei,Pi)(S_i,E_i,P_i)(Si,Ei,Pi)描述,(Si,Ei,Pi)(S_i,E_i,P_i)(Si,Ei,Pi)表示任务从第SiS_iSi秒开始,在第EiE_iEi秒后结束(第SiS_iSi秒和EiE_iEi秒任务也在运行),其优先级为PiP_iPi。同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同。调度系统会经常向查询系统询问,第XiX_iXi秒正在运行的任务中,优先级最小的KiK_iKi个任务(即将任务按照优先级从小到大排序后取前KiK_iKi个)的优先级之和是多少。特别的,如果KiK_iKi大于第XiX_iXi秒正在运行的任务总数,则直接回答第XiX_iXi秒正在运行的任务优先级之和。上述所有参数均为整数,时间的范围在111到nnn之间(包含111和nnn)。
输入格式
输入文件第一行包含两个空格分开的正整数mmm和nnn,分别表示任务总数和时间范围。接下来mmm行,每行包含三个空格分开的正整数SiS_iSi、EiE_iEi和Pi(Si≤Ei)Pi(S_i\le Ei)Pi(Si≤Ei),描述一个任务。接下来nnn行,每行包含四个空格分开的整数XiX_iXi、AiA_iAi、BiB_iBi和CiC_iCi,描述一次查询。查询的参数KiK_iKi需要由公式 Ki=1+(Ai∗Pre+Bi)mod  CiK_i=1+(A_i*Pre+B_i) \mod C_iKi=1+(Ai∗Pre+Bi)modCi计算得到。其中PrePrePre表示上一次查询的结果,对于第一次查询,Pre=1Pre=1Pre=1。
输出格式
输出共nnn行,每行一个整数,表示查询结果。
数据范围
对于100%的数据,1≤m,n,Si,Ei,Ci≤1051\le m,n,S_i,E_i,C_i\le 10^51≤m,n,Si,Ei,Ci≤105,0≤Ai,Bi≤1050\le A_i,B_i\le 10^50≤Ai,Bi≤105,1≤Pi≤1071\le P_i\le10^71≤Pi≤107,XiX_iXi为111到nnn的一个排列。
分析
看到第kkk小,第一反应联想到主席树,再一看题,发现有统一区间修改与统一单点询问,可以联想到差分。把这两个东西结合起来,就能过掉此题。
在一开始的修改中,对于Si,Ei,PiS_i,E_i,P_iSi,Ei,Pi,在SiS_iSi处PiP_iPi位置加1,Ei+1E_i+1Ei+1处PiP_iPi位置减1,然后再跑一遍前缀和,将当前节点的线段树与前一棵线段树以主席树的方式合并,即若当前节点没有值,则让当前节点指向前一棵树的对应节点,以节省空间。当然,本题要求求和,故加个和的值累加即可。若当前查询的KKK大于当前区间的总个数,直接返回当前区间的和。本题值域比较大,故要对PiP_iPi进行离散化。当递归到底部,即l==rl==rl==r时,不能直接返回当前的和,要返回当前位置对应的数的KKK个和。
若本题询问与查询混在一起,应该要用树状数组套主席树,动态维护主席树的前缀和。
代码
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
using namespace std;
const int N=400004,LogN=20;
const int INF=0x3f3f3f3f;
#define int long long
#define L(x) (nd[x].l)
#define R(x) (nd[x].r)
struct Node {
int sum,cnt;
int l,r;
}nd[N*LogN];
int rt[N],nn,tot;
int n,m,b[N],lastans;
int si[N],ei[N],pi[N];
void Add(int &p,int l,int r,int x,int v) {//更改
if (!p) p=++tot;
nd[p].cnt+=v;
nd[p].sum+=b[x]*v;
if (l==r) return;
int mid=(l+r)>>1;
if (x<=mid) Add(nd[p].l,l,mid,x,v);
else Add(nd[p].r,mid+1,r,x,v);
}
void Merge(int x,int&y,int l,int r) {//合并x->y
if (!y) {//没有对应节点,直接指向前一个节点
y=x;
return;
}
nd[y].cnt+=nd[x].cnt;//修改值
nd[y].sum+=nd[x].sum;
if (l==r) return;
int mid=(l+r)>>1;
if (nd[x].l)Merge(nd[x].l,nd[y].l,l,mid);//递归左右区间
if (nd[x].r) Merge(nd[x].r,nd[y].r,mid+1,r);
}
int Ask(int p,int l,int r,int k) {
if (nd[p].cnt<=k) return nd[p].sum;//询问小于个数
if (l==r) return (nd[p].sum/nd[p].cnt)*k;//k个当前数的和
int cnt=nd[L(p)].cnt;
int mid=(l+r)>>1;
if (cnt>=k) return Ask(L(p),l,mid,k);//左区间个数>=k,递归左区间
else return nd[L(p)].sum+Ask(R(p),mid+1,r,k-cnt);//否则递归右区间,求前k-cnt小的数的和+左区间的和
}
signed main() {
scanf("%lld%lld",&m,&n);
for (int i=1;i<=m;i++) {
scanf("%lld%lld%lld",&si[i],&ei[i],&pi[i]);
b[i]=pi[i];
}
sort(b+1,b+m+1);
nn=unique(b+1,b+m+1)-b-1;
for (int i=1;i<=m;i++) {
pi[i]=lower_bound(b+1,b+nn+1,pi[i])-b;//离散化
Add(rt[si[i]],1,nn,pi[i],1);//差分
Add(rt[ei[i]+1],1,nn,pi[i],-1);
}
for (int i=1;i<=n;i++)
Merge(rt[i-1],rt[i],1,nn);
lastans=1;
for (int i=1;i<=n;i++) {
int x,a,b,c,k;
scanf("%lld%lld%lld%lld",&x,&a,&b,&c);
k=1+(a*lastans+b)%c;
lastans=Ask(rt[x],1,nn,k);
printf("%lld\n",lastans);
}
return 0;
}