模拟赛(二):T118879 每日一摄(新东西:树状数组)
2020.2.6
模拟赛(二)
T2 每日一摄
题目背景
你是一名可爱的地学部成员。经过前辈教导,你已经学会了很多天文学知识,比如天球、星座、视星等、恒星时、太阳时等。现在你打算利用这些知识,在E星上拍摄几张美丽的星空照片。
题目描述
E星自转一圈需要T分钟。据此,我们将E星的天空分成T等份。每分钟的背景亮度为Hi。星星的亮度需要超过背景亮度才能被拍摄到。
一个星座是天球的一部分,星星的集合。第0天星座ii完全升起的时间为Li分钟,开始降下的时间为Ri分钟。每过一天,所有星座会向前平移B分钟,即早B分钟升起,早B分钟降下。所有Hi会向前平移B分钟,即H'[i]=H[(i+B)%T](H'为新的一天的亮度)。星座i的整体亮度为hi,美丽度为Wi。
你的相机是固定的,这意味着你每天只能在固定时间拍摄。每天,你的相机可以拍摄第XX分钟所有在天上的星星。设在第t天拍摄,被完整拍摄到的星座集合为S。那么这张照片的美丽度为−t+∑i∈SWi。
因为E星公转一周需要的时间为C天,所以你打算拍C次,每天一次。你需要计算这些照片的美丽度,以便从中选出几张使用。
输入格式
第一行输入n,T,B,X,C
接下来n行,每行4个数Li,Ri,hi,Wi
接下来一行T个数Hi,其中H0...HT-1表示每天每分钟的亮度
输出格式
输出C个数,表示第0天到第C-1天照片的美丽度。
输入输出样例
输入 #1
3 3 2 0 4
0 1 4 2
1 2 5 3
1 2 3 3
6 3 4
输出 #1
0 2 3 -3
输入 #2
14 14 13 11 14
4 6 3940583 444611423
3 7 54714199 232785225
1 2 83082480 689130122
6 13 453124829 191763737
0 8 93789229 182212274
0 5 146648299 757903050
0 13 139598801 150623060
2 11 492476376 147933099
0 9 273004317 135029029
5 13 281540370 41264140
10 12 858634371 633691621
1 11 419760703 671943851
8 13 619531983 435559792
7 12 434412441 447384263
606524423
92878780
478905555
937132827
546201006
441673224
430881921
625207382
836309814
379392007
894032522
882927437
132602801
998121111
输出 #2
0 -1 1894584740 -3 -4
题解:
因为星座和夜空同时进行前移,所以这题的前移B分钟其实就是时间后移B分钟,这就是相对论
所以只要预处理出第一天的每分钟的美丽度,就可以解决所有可能的情况了
使用枚举,分别枚举当前时刻星座i是否能计算。
自己的30‘代码:(70’TLE掉了)
因为是O(Tn)的复杂度,所以撑不过50万
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int n/*n个星座*/, T/*一天T分钟*/, B/*由于自转角造成季节偏转分钟数*/, X/*相机每日工作时间点*/, C/*一年C天*/, L[500005], R[500005]/*星座i出现在夜空的区间*/, h[500005]/*星座i亮度*/, W[500005]/*星座i美腻度*/, H[500005]/*第i分钟夜空亮度*/, Time;
long long Ans[500005] = { 0 }, Answer;
bool flag = 0;
int main() {
scanf("%d%d%d%d%d", &n, &T, &B, &X, &C);
for (int i = 1; i <= n; i++) {
scanf("%d%d%d%d", &L[i], &R[i], &h[i], &W[i]);
}
for (int i = 0; i < T; i++) {
scanf("%d", &H[i]);
}
for (int i = 0; i < T; i++) {//预处理第0天每分钟的美腻度(不计日期减成)
for (int j = 1; j <= n; j++) {//枚举星座
if ((L[j] <= i) && (R[j] >= i)) {//第i分钟j座在
if (h[j] > H[i]) {//j座比第i分钟夜空亮
Ans[i] += W[j];
}
}
}
}
Time = X;
for (int i = 0; i < C; i++) {//计算每天第X分钟的美腻度(顺便计算日期减成)
Answer = Ans[Time] - i;
if (flag) {
printf(" ");
}
printf("%lld", Answer);
Time += B;
Time %= T;
flag = 1;
}
printf("\n");
return 0;
}
O(Tn)是无法AC的(n方过不了50万),所以我们就要进行优化,因为每个星座只有亮度大于本分钟夜空亮度才能被显现,所以不妨将星座亮度和夜空亮度排序,这样就可以按照亮度的顺序求出每分钟的美丽度。
但是还要考虑星座左右边界的问题,所以需要维护一个差分数组,来实现在当前星座的区间内的区间修改操作,到时候只要求出从第一位到需要的分钟的前缀和就可以了
因为要维护一个差分数组的前缀和,所以我们可以使用树状数组来快速求出前缀和,使修改和询问都实现O(logn)的复杂度。
树状数组:(本次得分瓶颈)
其实我一开始是一窍不通的,研究了好久,总算理解了思想。
大体就是:用一个等长数组f[n+1]来存储原数组a[n+1]的不同区间的前缀和。(数组均以1位为首位,0位为空)
在此之前,引入一个“lowbit”的概念:
众所周知,计算机的数字以二进制存储。一个数字可以用01串表示,从右往左第i位的1表示2i-1。
lowbit的值,顾名思义,就是指一个二进制数存在1的最低位表示的数。比如00011000表示24,它的lowbit值就是8。
树状数组就是在第f[i]存储从a[i-lowbit(i)+1]到a[i],这一共lowbit(i)位的数字的前缀和.
要想快速地求出lowbit也不难,根据计算机采用补码记录负数(在负数的相反数的基础上,所有位都取反,然后再将左边首位(符号位)赋1,最后将这个数绝对值加一)可以发现,如果对一个自然数i和它的相反数-1按位与,所得到的值就是lowbit(i)。
只要用树状数组维护差分数组的前缀和,按照从亮到暗的顺序将星座的区间依次加上当前星座的美腻度然后同时查明当前比最暗星座暗的所有时刻,记录差分数组里当前时刻位的前缀和,最后得到第0天所有时刻的美腻度。复杂度O(Tlogn)
最后用30’的方法推得C天里第X分钟的美腻度即可,复杂度O(T)
正解标程简化加注:亲测AC
#include<algorithm>//注意:要开c++11才能编译
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
int n,T,B,X,C;
int L[500005],R[500005],h[500005],W[500005],id[500005],iid[500005],H[500005];
ll Ans[500005],tr[500005]/*将差分数组以树状数组的形式存储*/;
void mdy(int x,ll y){//树状数组的修改(第x位加y)
for(x++;x<=T;x+=x&-x)//将所有包含x的位都加上y(一开始x++的原因是:原数组是0~n-1的编号,但是树状数组是1~n的编号,所以要把编号加一)
tr[x]+=y;
}
ll qry(int x){//求出1-x的前缀和
ll ans=0;//一开始
for(x++;x;x-=x&-x)//枚举x及之前的区间(不大于log2(n)个)
ans+=tr[x];//累加
return ans;//返回结果
}
int main(){
scanf("%d%d%d%d%d",&n,&T,&B,&X,&C);
for(int i=1;i<=n;++i)
scanf("%d%d%d%d",&L[i],&R[i],&h[i],&W[i]);
for(int i=0;i<T;++i)
scanf("%d",&H[i]);
for(int i=1;i<=n;++i) {
id[i]=i;//预处理h编号
}
for(int i=0;i<T;++i) {
iid[i]=i;//处理H编号
}
sort(id+1,id+n+1,[&](int x,int y){return h[x]>h[y];});//这种表达是c++11才有的
sort(iid,iid+T,[&](int x,int y){return H[x]>H[y];});//根据数值排列编号
int j=0;
for(int i=1;i<=n;++i) {//枚举星座,从最亮的算起(这样就能保证最亮的(出现时)能在更多时候被看见)
while(j<T&&H[iid[j]]>=h[id[i]]) {//枚举时间,保证时刻iid[i]亮度大于id[i]星座,id[i]无法在本时刻显现(因为还没有执行到区间修改的语句,id[i]没有被加入查分序列)这时最后一个被加入查分数组的星座就是刚好可以在iid[i]时刻里显现的最暗星座。这样,亮度在j不断增加的时候不断减少,当id[i]的亮度大于iid[i]时刻的亮度时,就跳出循环,并且在数组里加入id[i]星座。此时数组里有什么星座,这些星座亮度都大于当前时刻亮度,可以被计算。
anses[iid[j]]=qry(iid[j]);//树状数组1-iid[j]位的元素的和就是iid[j]的时刻的美腻度
j++;//枚举下一个星座
}//这时,时刻亮度不及星座id[i]亮度,id[i]可以在接下来的时刻被显现,所以,在差分数组中加入id[i]
mdy(L[id[i]],W[id[i]]);
mdy(R[id[i]]+1,-W[id[i]]);//这两句是对树状差分数组的区间操作,将第i个星座在的时刻美腻度都加W[i]
}
while(j<T) {//如果最后时刻没有枚举完(这些时刻亮度太小,小于最暗的星座的时刻大于等于两个就会出现这种情况)
Ans[iid[j]]=qry(iid[j]);//处理剩下的时刻
j++;
}
for(int i=0;i<C;++i)
printf("%lld ",-i+Ans[(X+1ll*i*B)%T]);//输出时要注意日期减成
}