又是一道氪金题。。。
$BZOJ$氪金无极限。。。
附上大美洛谷的题面:
题目描述
OIVillage是一个风景秀美的乡村,为了更好的利用当地的旅游资源,吸引游客,推动经济发展,xkszltl决定修建了一条铁路将当地n个最著名的经典连接起来,让游客可以通过火车从铁路起点(1号景点)出发,依次游览每个景区。
为了更好的评价这条铁路,xkszltl为每一个景区都哦赋予了一个美观度,而一条旅行路径的价值就是它所经过的景区的美观度之和。
不过,随着天气与季节的变化,某些景点的美观度也会发生变化。
xkszltl希望为每位旅客提供最佳的旅行指导,但是由于游客的时间有限,不一定能游览全部景区,然而他们也不希望旅途过于短暂,所以每个游客都希望能在某一个区间内的车站结束旅程,而xkszltl的任务就是为他们选择一个终点使得旅行线路的价值最大。
可是当地的景点与前来观光的旅客实在是太多了,xkszltl无法及时完成任务,于是找到了准备虐杀NOI2011的你,希望你能帮助他完成这个艰巨的任务。
输入输出格式
输入格式:
第一行给出一个整数n,接下来一行给出n的景区的初始美观度。
第三行给出一个整数m,接下来m行每行为一条指令:
-
0 x y k:表示将x到y这段铁路边上的景区的美观度加上k;
-
1 x y:表示有一名旅客想要在x到y这段(含x与y)中的某一站下车,你需要告诉他最大的旅行价值。
输出格式:
对于每个询问,输出一个整数表示最大的旅行价值。
输入输出样例
说明
对于100%的数据,n,m≤100000。
题解Here!
题意就是求从$1$开始,到$[x,y]$之间某个点结束的前缀和最大的那个值,带修改。
那个修改先丢一边去。
有一个不容易想到的转化:
如果我们把下标看做横坐标,前缀和看做纵坐标,那答案肯定是在凸包的最高点上。
所以关键在于动态维护凸包。
我们有许多极好的数据结构可以动态维护凸包:$Treap$,李超树,$set$,等等。
但是这里我们选择了分块+二分维护。
为什么使用复杂度更高的算法呢?
因为我们维护的是前缀和及其修改。
如果是原序列,区间加就直接打个标记就好。
但是现在是前缀和。
于是区间加就变成了区间加首项为$k$、公差为$k$的等差数列。
但是多次修改呢?
没事,我们有高中必修:
$$\text{等差数列}\{a_i\}+\text{等差数列}\{b_i\}=\text{等差数列}\{a_i+b_i\}$$
所以直接合并等差数列就好。
对于每个块,我们维护等差数列首项$first$、公差$d$以及一个$add$标记。
于是每个位置的真实值就是:$$\text{当前值}+\text{块首项}+\text{公差}\times(\text{当前位置}-\text{块左端位置})+add$$
但是注意,我们维护的是前缀和。
所以当我们在$[l,r]$上区间加时,对于$r$之后的那些块,我们都要打上$add$标记。
而且在处理$r$所属块时,要先把区间加对在$r$之后的$r$所属块中元素的影响考虑完,才能对$r$所属块维护凸包。
复杂度$O(n\sqrt n\log_2 n)$。
记得开$long\ long$。
附代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define MAXN 100010
#define MAXM 320
#define MAX (1LL<<61)
using namespace std;
int n,m,block;
int colour[MAXN],Left[MAXM],Right[MAXM],num[MAXM],convex[MAXM][MAXM];
int top,stack[MAXM];
long long val[MAXN],add[MAXM],first[MAXM],d[MAXM];//first item,tolerance
inline int read(){
int date=0,w=1;char c=0;
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
return date*w;
}
inline double slope(int x,int y){
return 1.000*(val[x]-val[y])/(x-y);
}
void build(int x){
top=0;
stack[++top]=Left[x];
for(int i=Left[x]+1;i<=Right[x];i++){
while(top>=2&&slope(stack[top-1],stack[top])<slope(stack[top-1],i))top--;
stack[++top]=i;
}
num[x]=top;
convex[x][0]=0;convex[x][top+1]=n+1;
for(int i=1;i<=top;i++)convex[x][i]=stack[i];
add[x]=first[x]=d[x]=0;
}
void pushdown(int x){
long long now=first[x];
for(int i=Left[x];i<=Right[x];i++){
val[i]+=add[x]+now;
now+=d[x];
}
add[x]=first[x]=d[x]=0;
}
void update(int l,int r,long long k){
long long sum;
if(colour[l]==colour[r]){
pushdown(colour[l]);
sum=k;
for(int i=l;i<=r;i++){
val[i]+=sum;
sum+=k;
}
sum=k*(r-l+1);
for(int i=r+1;i<=Right[colour[r]];i++)val[i]+=sum;
build(colour[l]);
for(int i=colour[r]+1;i<=colour[n];i++)add[i]+=sum;
}
else{
sum=k*(Left[colour[l]+1]-l+1);
for(int i=colour[l]+1;i<colour[r];i++){
first[i]+=sum;
d[i]+=k;
sum+=k*block;
}
pushdown(colour[l]);
sum=k;
for(int i=l;i<=Right[colour[l]];i++){
val[i]+=sum;
sum+=k;
}
build(colour[l]);
pushdown(colour[r]);
sum=k*(Left[colour[r]]-l+1);
for(int i=Left[colour[r]];i<=r;i++){
val[i]+=sum;
sum+=k;
}
sum=k*(r-l+1);
for(int i=r+1;i<=Right[colour[r]];i++)val[i]+=sum;
build(colour[r]);
for(int i=colour[r]+1;i<=colour[n];i++)add[i]+=sum;
}
}
inline long long query_point(int x){
if(x==0||x==n+1)return -MAX;
return val[x]+first[colour[x]]+d[colour[x]]*(x-Left[colour[x]])+add[colour[x]];
}
long long query_block(int x){
int l=1,r=num[x],mid;
long long a1,a2,a3;
while(l<=r){
mid=l+r>>1;
a1=query_point(convex[x][mid-1]);
a2=query_point(convex[x][mid]);
a3=query_point(convex[x][mid+1]);
if(a1<a2&&a2<a3)l=mid+1;
else{
if(a1>a2&&a2>a3)r=mid-1;
else return a2;
}
}
return -MAX;
}
long long solve(int l,int r){
long long ans=-MAX;
if(colour[l]==colour[r])for(int i=l;i<=r;i++)ans=max(ans,query_point(i));
else{
for(int i=colour[l]+1;i<colour[r];i++)ans=max(ans,query_block(i));
for(int i=l;i<=Right[colour[l]];i++)ans=max(ans,query_point(i));
for(int i=Left[colour[r]];i<=r;i++)ans=max(ans,query_point(i));
}
return ans;
}
void work(){
int f,x,y;
long long k;
while(m--){
f=read();x=read();y=read();
if(f==0){
k=read();
update(x,y,k);
}
else printf("%lld\n",solve(x,y));
}
}
void init(){
n=read();
val[0]=0;
for(int i=1;i<=n;i++)val[i]=val[i-1]+read();
val[0]=val[n+1]=-MAX;
block=sqrt(n);
for(int i=1;i<=n;i++){
colour[i]=(i-1)/block+1;
if(!Left[colour[i]])Left[colour[i]]=i;
Right[colour[i]]=i;
}
for(int i=1;i<=colour[n];i++)build(i);
m=read();
}
int main(){
init();
work();
return 0;
}