原题面
Description
最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分。超级计算机中的任务用三元组 ( S i , E i , P i ) (S_i,E_i,P_i) (Si,Ei,Pi)描述, ( S i , E i , P i ) (S_i,E_i,P_i) (Si,Ei,Pi)表示任务从第 S i S_i Si秒开始,在第 E i E_i Ei秒后结束(第 S i S_i Si秒和 E i E_i Ei秒任务也在运行),其优先级为 P i P_i Pi。同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同。调度系统会经常向查询系统询问,第 X i X_i Xi秒正在运行的任务中,优先级最小的 K i K_i Ki个任务(即将任务按照优先级从小到大排序后取前 K i K_i Ki个)的优先级之和是多少。特别的,如果 K i K_i Ki大于第 X i X_i Xi秒正在运行的任务总数,则直接回答第 X i X_i Xi秒正在运行的任务优先级之和。上述所有参数均为整数,时间的范围在 1 1 1到 n n n之间(包含 1 1 1和 n n n)。
Input
输入文件第一行包含两个空格分开的正整数 m m m和 n n n,分别表示任务总数和时间范围。接下来 m m m行,每行包含三个空格分开的正整数 S i S_i Si、 E i E_i Ei和 P i P_i Pi( S i S_i Si<= E i E_i Ei),描述一个任务。接下来 n n n行,每行包含四个空格分开的整数 X i X_i Xi、 A i A_i Ai、 B i B_i Bi和 C i C_i Ci,描述一次查询。查询的参数 K i K_i Ki需要由公式 K i K_i Ki= 1 + ( A i ∗ P r e + B i ) m o d C i 1+(A_i*Pre+B_i) mod C_i 1+(Ai∗Pre+Bi)modCi计算得到。其中 P r e Pre Pre表示上一次查询的结果,对于第一次查询, P r e = 1 Pre=1 Pre=1。
Output
输出共 n n n行,每行一个整数,表示查询结果。
Solution
好题,让人深入而透彻的理解主席树。
0 p t s 0pts 0pts做法
拿到题,最初的想法是什么?显然是暴力加入任务,维护时间轴,而每个任务都可以代表着一段区间,自然而然地想到各种维护区间的数据结构,例如分块线段树。稍稍看一看题面就可以知道,这个其实不太可能对一段时间维护,因为每一段内的优先级们太混乱了,那么就只可能对一个时间点进行维护,因而一个任务需要
E
i
−
S
i
E_i-S_i
Ei−Si棵线段树才能够维护,因而你会
M
L
E
MLE
MLE,一分也没有。
100 p t s 100pts 100pts做法
上面那个算法虽然是爆零算法,但是他给我们指了一条路:用一些线段树去搞定每个任务。那么如何有效的减少空间消耗呢?注意到,一个任务是一段区间,那么这些任务们可能会有很多重叠的地方,那么我们能不能吧这些重叠的保留下来,而只新建几个节点呢?都想到这里了,那么解法就很清晰了:主席树。
但是,还有一个问题,一个任务对于这棵主席树而言是一个区间修改,似乎主席树不能这么干,怎么办?注意到主席树建树利用了前缀和的思想,那么这个区间修改也可以用同样的思想,这样才具有可移植性与可维护性。要用前缀和去实现区间修改,显然使用差分啊,所以本题就分析完了,得到如下算法:
对于每个任务,维护两棵权值线段树(想一想,为什么?),分别是
E
i
E_i
Ei,
S
i
+
1
S_i+1
Si+1这两个时间点,在
E
i
E_i
Ei处
+
1
+1
+1,在
S
i
+
1
S_i+1
Si+1出
−
1
-1
−1。
然后按照时间先后顺序,将这些权值线段树
m
e
r
g
e
merge
merge成一棵主席树,然后就可以十分轻松的进行
q
u
e
r
y
query
query了。
Talk is Cheap, Show You the Code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 100005
int read(){
int sum=0,neg=1;
char c=getchar();
while(c>'9'||c<'0'&&c!='-') c=getchar();
if(c=='-') neg=-1,c=getchar();
while(c>='0'&&c<='9') sum=(sum<<1)+(sum<<3)+c-'0',c=getchar();
return sum*neg;
}
int m,n,a[N],rt[N*100],tot,lson[N*100],rson[N*100],cnt[N*100],sum[N*100];
struct Event{
int s,e,p;
}eve[N];
inline void pushup(int now){
sum[now]=sum[lson[now]]+sum[rson[now]];
cnt[now]=cnt[lson[now]]+cnt[rson[now]];//计算优先级出现的次数
}
void build(int &now,int l,int r,int pos,int delta){
if(!now) now=++tot;
if(l==r){
cnt[now]+=delta;
sum[now]=cnt[now]*a[l];
return;
}
int mid=(l+r)>>1;
if(pos<=mid) build(lson[now],l,mid,pos,delta);
else build(rson[now],mid+1,r,pos,delta);
pushup(now);
}
int merge(int x,int y){//merge返回新的节点的编号
if(!x||!y) return x+y;
int now=++tot;
sum[now]=sum[x]+sum[y];
cnt[now]=cnt[x]+cnt[y];
lson[now]=merge(lson[x],lson[y]);
rson[now]=merge(rson[x],rson[y]);
return now;
}
int query(int now,int l,int r,int k){
if(l==r) return a[l]*min(k,cnt[now]);//若k比现在的优先级数量还多的话,只能算数量次
int mid=(l+r)>>1;
if(cnt[lson[now]]>=k) return query(lson[now],l,mid,k);//在左儿子中查询
else return sum[lson[now]]+query(rson[now],mid+1,r,k-cnt[lson[now]])//在右儿子查询是,要注意加上左儿子对和的贡献,不能只算右儿子
}
signed main(){
m=read(); n=read();
//cout<<m<<" "<<n<<endl;
for(int i=1;i<=m;i++){
eve[i].s=read();
eve[i].e=read();
eve[i].p=read();
a[i]=eve[i].p;
}
sort(a+1,a+m+1);
++n;
int num=unique(a+1,a+m+1)-a-1;
//cout<<num<<endl;
for(int i=1;i<=m;i++){
eve[i].p=lower_bound(a+1,a+num+1,eve[i].p)-a;
build(rt[eve[i].s],1,num,eve[i].p,1);
build(rt[eve[i].e+1],1,num,eve[i].p,-1);
}
for(int i=1;i<=n;i++) rt[i]=merge(rt[i-1],rt[i]);
int pre=1;
for(int i=1;i<n;i++){
int x,a,b,c,k;
x=read(),a=read(),b=read(),c=read();
k=1+(a*pre+b)%c;
pre=query(rt[x],1,num,k);
printf("%d\n",pre);
}
return 0;
}
格式难看勿喷,优快云吞缩进!!!