题意:长度为 n n n的序列, q q q次询问,每次给定一个区间,钦定区间中的一个位置 x x x,使得区间所有点 与 x x x之间的最大值(含端点) 之和 最小,输出最小值。
n , q ≤ 7.5 × 1 0 5 n,q\leq7.5\times10^5 n,q≤7.5×105
神仙题,不愧是IOI
首先有一个 O ( n 2 ) O(n^2) O(n2)的 dp
f ( l , r ) = min { f ( l , k − 1 ) + ( r − k + 1 ) h k , ( k − l + 1 ) h k + f ( k + 1 , r ) } f(l,r)=\min\{f(l,k-1)+(r-k+1)h_k,(k-l+1)h_k+f(k+1,r)\} f(l,r)=min{f(l,k−1)+(r−k+1)hk,(k−l+1)hk+f(k+1,r)}
其中 k k k是 l , r l,r l,r中的最大值的位置(如有多个随便选一个),即讨论钦定的点在最大值的左侧或右侧,然后另一侧的点贡献都是最大值
我觉得我考场上能想到这步就不错了
这个 dp 转移已经 O ( 1 ) O(1) O(1)了,也不好压成一维,所以要么用可持久化之类的东西强行压状态,要么就不记录无用的状态
直接想的话两条路都不好走,但注意到这个过程实际上是最值分治,自然地想到笛卡尔树
哪里自然了啊kora
具体地讲,建出序列的笛卡尔树,然后在树上做上面dp,每个点只记录它代表的区间的dp值,这样就可以 O ( n ) O(n) O(n)处理出来了。
然而就算处理出来了你仍然无法快速计算答案,因为询问区间可能会被拆成很多小段,你需要像平衡树一样沿着树递归下去。而笛卡尔树高是 O ( n ) O(n) O(n)的,仍然可以被卡成狗。
不过思路感觉很对,考虑怎么优化
观察一下这个dp方程式
f ( l , r ) = min { f ( l , k − 1 ) + ( r − k + 1 ) h k , f ( k + 1 , r ) + ( k − l + 1 ) h k } f(l,r)=\min\{f(l,k-1)+(r-k+1)h_k,f(k+1,r)+(k-l+1)h_k\} f(l,r)=min{f(l,k−1)+(r−k+1)hk,f(k+1,r)+(k−l+1)hk}
套到树上:当前子树根结点是 k k k,为了计算 f ( l , k − 1 ) f(l,k-1) f(l,k−1)和 f ( k + 1 , r ) f(k+1,r) f(k+1,r),我们需要继续往左右子树递归计算,我们这样子是不行的
但这个 f ( l , k − 1 ) f(l,k-1) f(l,k−1)和 f ( k + 1 , r ) f(k+1,r) f(k+1,r)比较特殊:它们都有一个端点是固定的!
为了叙述方便,下面只讨论 f ( k + 1 , r ) f(k+1,r) f(k+1,r),左边的 f ( l , k − 1 ) f(l,k-1) f(l,k−1)是同理的
我们要是知道右子树的区间的所有前缀的dp信息就好了
看上去很扯,但实际上是可行的!
假设我们分别知道 k k k的左右子树的前缀信息,也就是知道 f ( l , l . . . k − 1 ) f(l,l...k-1) f(l,l...k−1)和 f ( k + 1 , k + 1... r ) f(k+1,k+1...r) f(k+1,k+1...r),现在考虑怎么合并 f ( l , l . . . r ) f(l,l...r) f(l,l...r)
显然左边是不用管的
对于右边,我们再把这个方程式搬出来。为了看着顺眼,我把 r r r换成了 i i i
f ( l , i ) = min { f ( l , k − 1 ) + ( i − k + 1 ) h k , f ( k + 1 , i ) + ( k − l + 1 ) h k } f(l,i)=\min\{f(l,k-1)+(i-k+1)h_k,f(k+1,i)+(k-l+1)h_k\} f(l,i)=min{f(l,k−1)+(i−k+1)hk,f(k+1,i)+(k−l+1)hk}
注意这个 i i i是在 [ k + 1 , r ] [k+1,r] [k+1,r]内的,冷静分析一下,发现这个东西就是在原来的基础上整体加一个数,然后和一个一次函数取 min \min min,因为是个区间,所以可以用线段树维护!
而这个方程是可以找到一个分界点,使得分界点左边取左边的值,右边取右边的值。原因是 i i i每增加 1 1 1,左边的值固定增加 h k h_k hk,而右边增加 f ( k + 1 , i + 1 ) − f ( k + 1 , i ) f(k+1,i+1)-f(k+1,i) f(k+1,i+1)−f(k+1,i),区间往右扩张一位增加的代价总不可能大于区间最大值吧……所以左边迟早会超过右边,而且不会被反超。线段树上二分即可。
什么?线段树开不下?
但仔细想想会发现不同的位置是不会冲突的,即每个点 x x x维护它当前处理的左端点到 x x x的dp值,所以开一个线段树就可以了。
线段树合并应该也可以
f ( l , k − 1 ) f(l,k-1) f(l,k−1)的话再开一棵线段树,左右倒过来就可以了
然后把询问离线,每组询问挂在区间最大值的点上面,建笛卡尔树的时候顺便处理一下就可以了。
复杂度 O ( n log n ) O(n\log n) O(nlogn)
#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <vector>
#define MAXN 750005
using namespace std;
inline int read()
{
int ans=0;
char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
typedef long long ll;
int n;
struct SegmentTree
{
#define lc p<<1
#define rc p<<1|1
struct node
{
int l,r,tag;
ll k,b,lv,rv;
}t[MAXN<<2];
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
if (l==r) return;
int mid=(l+r)>>1;
build(lc,l,mid),build(rc,mid+1,r);
}
inline void pushcov(int p,ll k,ll b)
{
t[p].k=k,t[p].b=b;
t[p].lv=k*t[p].l+b,t[p].rv=k*t[p].r+b;
t[p].tag=1;
}
inline void pushadd(int p,ll k,ll b)
{
t[p].k+=k,t[p].b+=b;
t[p].lv+=k*t[p].l+b,t[p].rv+=k*t[p].r+b;
if (!t[p].tag) t[p].tag=2;
}
inline void pushdown(int p)
{
if (t[p].tag)
{
if (t[p].tag==1) pushcov(lc,t[p].k,t[p].b),pushcov(rc,t[p].k,t[p].b);
if (t[p].tag==2) pushadd(lc,t[p].k,t[p].b),pushadd(rc,t[p].k,t[p].b);
t[p].tag=t[p].k=t[p].b=0;
}
}
inline void update(int p){t[p].lv=t[lc].lv,t[p].rv=t[rc].rv;}
ll query(int p,int k)
{
if (t[p].l==t[p].r) return t[p].lv;
pushdown(p);
if (k<=t[lc].r) return query(lc,k);
return query(rc,k);
}
void search(int p,int l,int r,ll k1,ll b1,ll b2)
{
if (l<=t[p].l&&t[p].r<=r)
{
if (k1*t[p].l+b1<=t[p].lv+b2&&k1*t[p].r+b1<=t[p].rv+b2) return pushcov(p,k1,b1);
if (t[p].lv+b2<=k1*t[p].l+b1&&t[p].rv+b2<=k1*t[p].r+b1) return pushadd(p,0,b2);
}
if (r<t[p].l||t[p].r<l) return;
pushdown(p);
search(lc,l,r,k1,b1,b2),search(rc,l,r,k1,b1,b2);
update(p);
}
}lt,rt;
int h[MAXN];
inline int Max(const int& x,const int& y){return h[x]>h[y]? x:y;}
int st[20][MAXN],LOG[MAXN];
inline int rmq(int l,int r)
{
int t=LOG[r-l+1];
return Max(st[t][l],st[t][r-(1<<t)+1]);
}
int ql[MAXN],qr[MAXN];
ll ans[MAXN];
vector<int> lis[MAXN];
void solve(int l,int r)
{
if (l>r) return;
int k=rmq(l,r);
solve(l,k-1),solve(k+1,r);
for (int i=0;i<(int)lis[k].size();i++)
{
int L=ql[lis[k][i]],R=qr[lis[k][i]];
ans[lis[k][i]]=h[k]*(R-L+1ll);
if (L<k) ans[lis[k][i]]=min(ans[lis[k][i]],rt.query(1,L)+(R-k+1ll)*h[k]);
if (k<R) ans[lis[k][i]]=min(ans[lis[k][i]],(k-L+1ll)*h[k]+lt.query(1,R));
}
ll tl=rt.query(1,l),tr=lt.query(1,r);
lt.search(1,k,r,h[k],tl+h[k]*(1ll-k),(k-l+1ll)*h[k]);
rt.search(1,l,k,-h[k],tr+h[k]*(k+1ll),(r-k+1ll)*h[k]);
}
int main()
{
n=read();
int q=read();
for (int i=1;i<=n;i++) h[i]=read();
lt.build(1,1,n);rt.build(1,1,n);
for (int i=1;i<=n;i++) st[0][i]=i;
for (int j=1;j<20;j++)
for (int i=1;i+(1<<(j-1))<=n;i++)
st[j][i]=Max(st[j-1][i],st[j-1][i+(1<<(j-1))]);
LOG[0]=-1;
for (int i=1;i<=n;i++) LOG[i]=LOG[i>>1]+1;
for (int i=1;i<=q;i++) ql[i]=read()+1,qr[i]=read()+1,lis[rmq(ql[i],qr[i])].push_back(i);
solve(1,n);
for (int i=1;i<=q;i++) printf("%lld\n",ans[i]);
return 0;
}