BZOJ2388: 旅行规划
又是一道氪金题。。。
$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; }