正题
P5044
题目大意
给出一个序列a,设 dist(x,y)=maxi=xyaidist(x,y)=\max_{i=x}^ya_idist(x,y)=maxi=xyai,有m个询问,对于每个询问,给出 l,r,让你找一个点x(l≤x≤r)(l\leq x\leq r)(l≤x≤r),使得 ∑i=lrdist(i,x)\sum_{i=l}^rdist(i,x)∑i=lrdist(i,x) 最小
解题思路
设 fi,jf_{i,j}fi,j 为区间 [l,r] 的答案,那么得到区间最大值x后,可以按如下转移
fl,r=min(fl,x−1+ax×(r−x+1),fx+1,r+ax×(x−l+1))f_{l,r}=\min(f_{l,x-1}+a_x\times(r-x+1),f_{x+1,r}+a_x\times(x-l+1))fl,r=min(fl,x−1+ax×(r−x+1),fx+1,r+ax×(x−l+1))
考虑如何优化
考虑对该数列建立笛卡尔树
对于一个查询 [l,r] ,求出最大点x,那么可以把当前询问拆成 [l,x-1] 和 [x+1,r]
那么对于笛卡尔树上的一个点x(子树范围为 [l,r]),只需维护 [l,x-1],[l+1,x-1]…[x-1,x-1] 和 [x+1,r],[x+1,r-1]…[x+1,x+1] 这些状态
那么可以用两个线段树,分别维护遍历到当前点时每个点到子树最左/右端的答案
对于左子树的点,考虑从左子树选还是从右子树选,可以把右子树选的贡献差分一下,然后线段树上二分找分界点修改决策(即哪些点选左子树,哪些点选右子树,单调性易证),右子树同理
遍历完笛卡尔树中左右子树后,计算最大点为当前点的询问,记下答案后再往回走
时间复杂度 O((n+q)logn)O((n+q)\ log\ n)O((n+q) log n)
code
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define N 750100
#define mp make_pair
#define fs first
#define sn second
using namespace std;
ll n,t,l,r,lg[N],h[N],f[N][25],ans[N];
vector<pair<pair<ll,ll>,ll> >q[N];
struct Tree
{#define ls x*2#define rs x*2+1ll lazy[N<<2],b[N<<2],k[N<<2],lm[N<<2],rm[N<<2];void push_up(ll x){lm[x]=lm[ls];rm[x]=rm[rs];return;}void get(ll x,ll l,ll r,ll bb,ll kk){b[x]+=bb;k[x]+=kk;lm[x]+=bb+kk*l;rm[x]+=bb+kk*r;return;}void clr(ll x){lazy[x]=1;k[x]=b[x]=lm[x]=rm[x]=0;return;}void push_down(ll x,ll l,ll r){if(lazy[x]){clr(ls);clr(rs);lazy[x]=0;}ll mid=l+r>>1;get(ls,l,mid,b[x],k[x]);get(rs,mid+1,r,b[x],k[x]);b[x]=k[x]=0;return;}void add(ll x,ll L,ll R,ll l,ll r,ll y){if(L==l&&R==r){get(x,l,r,y,0);return;}push_down(x,L,R);ll mid=L+R>>1;if(r<=mid)add(ls,L,mid,l,r,y);else if(l>mid)add(rs,mid+1,R,l,r,y);else add(ls,L,mid,l,mid,y),add(rs,mid+1,R,mid+1,r,y);push_up(x);return;}void change(ll x,ll L,ll R,ll l,ll r,ll y,ll z){if(L==l&&R==r){ll gl=y+z*l,gr=y+z*r;if(gl>=lm[x]&&gr>=rm[x])return;//分界点在中间就二分下去else if(gl<=lm[x]&&gr<=rm[x]){clr(x);get(x,l,r,y,z);return;}}push_down(x,L,R);ll mid=L+R>>1;if(r<=mid)change(ls,L,mid,l,r,y,z);else if(l>mid)change(rs,mid+1,R,l,r,y,z);else change(ls,L,mid,l,mid,y,z),change(rs,mid+1,R,mid+1,r,y,z);push_up(x);return;}ll ask(ll x,ll l,ll r,ll y){if(l==r)return lm[x];push_down(x,l,r);ll mid=l+r>>1;if(y<=mid)return ask(ls,l,mid,y);else return ask(rs,mid+1,r,y);}
}TL,TR;
ll get(ll l,ll r)
{ll g=lg[r-l+1];if(h[f[l][g]]>=h[f[r-(1<<g)+1][g]])return f[l][g];else return f[r-(1<<g)+1][g];
}
void solve(ll l,ll r)
{ll x=get(l,r);if(l<x)solve(l,x-1);if(x<r)solve(x+1,r);for(ll i=0;i<q[x].size();++i){ll lans=0,rans=0,L=q[x][i].fs.fs,R=q[x][i].fs.sn;if(L<x)lans=TR.ask(1,1,n,L);if(x<R)rans=TL.ask(1,1,n,R);ans[q[x][i].sn]=min(lans+h[x]*(R-x+1),rans+h[x]*(x-L+1));}ll gl=h[x],gr=h[x];if(l<x)gl+=TL.ask(1,1,n,x-1);if(x<r)gr+=TR.ask(1,1,n,x+1);TL.add(1,1,n,x,x,gl);TR.add(1,1,n,x,x,gr);if(l<x){TR.add(1,1,n,l,x-1,h[x]*(r-x+1));//加上右子树的点走到左边的贡献TR.change(1,1,n,l,x-1,gr+h[x]*x,-h[x]);//在右边选的贡献}if(x<r){TL.add(1,1,n,x+1,r,h[x]*(x-l+1));TL.change(1,1,n,x+1,r,gl-h[x]*x,h[x]);}return;
}
int main()
{scanf("%lld%lld",&n,&t);for(ll i=1;i<=n;++i){scanf("%lld",&h[i]);f[i][0]=i;}for(ll i=2;i<=n;++i)lg[i]=lg[i>>1]+1;for(ll j=1;j<=20;++j)for(ll i=1;i<=n-(1<<j)+1;++i)if(h[f[i][j-1]]>=h[f[i+(1<<j-1)][j-1]])f[i][j]=f[i][j-1];else f[i][j]=f[i+(1<<j-1)][j-1];for(ll i=1;i<=t;++i){scanf("%lld%lld",&l,&r);l++;r++;q[get(l,r)].push_back(mp(mp(l,r),i));}solve(1,n);for(ll i=1;i<=t;++i)printf("%lld\n",ans[i]);return 0;
}