题目
题目链接:https://www.luogu.com.cn/problem/P5044
有 (N) 座山横着排成一行,从左到右编号为从 (0) 到 (N-1)。山的高度为 (H_i)((0leq ileq N-1))。每座山的顶上恰好住着一个人。
你打算举行 (Q) 个会议,编号为从 (0) 到 (Q-1)。会议 (j)((0leq jleq Q-1)) 的参加者为住在从山 (L_j) 到山 (R_j)(包括 (L_j) 和 (R_j))上的人((0leq L_jleq R_jleq N-1))。对于该会议,你必须选择某个山 (x) 做为会议举办地((L_jleq xleq R_j))。举办该会议的成本与你的选择有关,其计算方式如下:
- 来自每座山 (y)((L_jleq yleq R_j)) 的参会者的成本,等于在山 (x) 和 (y) 之间(包含 (x) 和 (y))的所有山的最大高度。特别地,来自山 (x) 的参会者的成本是 (H_x),也就是山 (x) 的高度。
- 会议的成本等于其所有参会者的成本之和。
你想要用最低的成本来举办每个会议。
注意,所有的参会者将在每次会议后回到他们自己的山;所以一个会议的成本不会受到先前会议的影响。
(n,Qleq 7.5 imes 10^5)。
思路
首先考虑一个朴素的 (O(n^2)) dp。设 (f[l][r]) 表示区间 ([l,r]) 内所有人聚到一起的最小代价。找到区间 ([l,r]) 的最大值 (mid),那么有
也就是考虑是最大值左边的人去右边还是右边的人去左边。
我们发现每次转移都和区间里的最大值 (mid) 有关,考虑在笛卡尔树上进行 dp。
把每一个询问 ([l,r]) 拆成两个询问 ([l,mid-1],[mid+1,r]),其中 (mid) 是区间 ([l,r]) 的最大值。这样的话拆出来的第一个询问一定是笛卡尔树一个节点的后缀,第二个询问一定是笛卡尔树一个节点的前缀。分别求出后可以再直接套用上面的转移式一次求出答案。
我们只讨论拆出来的第二个询问,第一个询问可以把数组全部取反后再跑一次得出。
维护一棵线段树,其中节点 ([i,i]) 表示在目前 dp 到的笛卡尔树节点内,区间 ([l,i]) 的 dp 值。其中 (i) 被包含在笛卡尔树上 (mid) 所代表的区间。
假设我们已经递归处理好 ([l,mid-1]) 和 ([mid+1,r]) 了,考虑如何将信息合并到 ([l,r]) 上。
看回上面的转移式。我们发现,在 (l) 固定,(r) 不断增大的情况下,(min) 内左边的式子每次增加 (a[mid]),右边的式子每次增加的值一定不超过 (a[mid])(增加的一定是右子树的某一个值,根据笛卡尔树的性质,这个值肯定不超过 (a[mid]))。所以在当前区间 ([l,r]) 内,([l,mid-1]) 已经在左儿子处计算过了,一定存在一个位置 (p),使得 ([mid,p]) 全都是由 (min) 内左边的式子转移而来,([p+1,r]) 全都是由 (min) 内右边的式子转移而来。
所以我们可以通过在线段树上二分来找到这一个位置 (p)。只需要维护每一个区间右端点的值即可。
找到这个位置后,([mid,p]) 的 dp 值就是覆盖为一个等差数列,([p+1,r]) 的 dp 值就是全部加上一个定值。所以我们线段树只需要支持区间覆盖,区间家等差数列,并维护区间右端点的值即可。
注意我们翻转区间后需要保证笛卡尔树的形态不变,由于本题的山的高度可能相同,所以在写 ST 表的时候,翻转后原本的 (<) 需要变成 (leq) 来保证笛卡尔树的形态。
时间复杂度 (O((n+Q)log n))。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
const int N=750010,LG=20;
int n,Q,ID,a[N],lg[N],st[N][LG];
vector<int> ask[N];
struct node
{
int l,r;
ll f[2];
}b[N];
void buildst()
{
for (int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
for (int i=1;i<=n;i++) st[i][0]=i;
for (int i=n;i>=1;i--)
for (int j=1;i+(1<<j)-1<=n;j++)
if (ID) st[i][j]=(a[st[i][j-1]]>a[st[i+(1<<j-1)][j-1]]) ? st[i][j-1] : st[i+(1<<j-1)][j-1];
else st[i][j]=(a[st[i][j-1]]>=a[st[i+(1<<j-1)][j-1]]) ? st[i][j-1] : st[i+(1<<j-1)][j-1];
}
int findmax(int l,int r)
{
int k=lg[r-l+1];
if (ID) return (a[st[l][k]]>a[st[r-(1<<k)+1][k]]) ? st[l][k] : st[r-(1<<k)+1][k];
else return (a[st[l][k]]>=a[st[r-(1<<k)+1][k]]) ? st[l][k] : st[r-(1<<k)+1][k];
}
struct SegTree
{
ll lazy[N*4][3],val[N*4];
void pushdown(int x,int l,int r)
{
if (lazy[x][0])
{
lazy[x*2][0]=lazy[x*2+1][0]=1;
lazy[x*2][1]=lazy[x*2][2]=lazy[x*2+1][1]=lazy[x*2+1][2]=0;
lazy[x][0]=val[x*2]=val[x*2+1]=0;
}
if (lazy[x][1] || lazy[x][2])
{
int mid=(l+r)>>1;
lazy[x*2][1]+=lazy[x][1];
lazy[x*2][2]+=lazy[x][2];
val[x*2]+=lazy[x][1]+lazy[x][2]*(mid-l);
lazy[x*2+1][1]+=lazy[x][1]+lazy[x][2]*(mid-l+1);
lazy[x*2+1][2]+=lazy[x][2];
val[x*2+1]+=lazy[x][1]+lazy[x][2]*(r-l);
lazy[x][1]=lazy[x][2]=0;
}
}
void pushup(int x)
{
val[x]=val[x*2+1];
}
void update1(int x,int l,int r,int ql,int qr)
{
if (ql>qr) return;
if (ql<=l && qr>=r)
{
lazy[x][0]=1; lazy[x][1]=lazy[x][2]=val[x]=0;
return;
}
pushdown(x,l,r);
int mid=(l+r)>>1;
if (ql<=mid) update1(x*2,l,mid,ql,qr);
if (qr>mid) update1(x*2+1,mid+1,r,ql,qr);
pushup(x);
}
void update2(int x,int l,int r,int ql,int qr,ll v1,ll v2)
{
if (ql>qr) return;
if (ql<=l && qr>=r)
{
lazy[x][1]+=v1; lazy[x][2]+=v2;
val[x]+=v1+v2*(r-l);
return;
}
pushdown(x,l,r);
int mid=(l+r)>>1;
if (qr<=mid) update2(x*2,l,mid,ql,qr,v1,v2);
else if (ql>mid) update2(x*2+1,mid+1,r,ql,qr,v1,v2);
else update2(x*2,l,mid,ql,mid,v1,v2),update2(x*2+1,mid+1,r,mid+1,qr,v1+v2*(mid-ql+1),v2);
pushup(x);
}
int query1(int x,int l,int r,int k)
{
if (l==r) return val[x];
pushdown(x,l,r);
int mid=(l+r)>>1;
if (k<=mid) return query1(x*2,l,mid,k);
else return query1(x*2+1,mid+1,r,k);
}
int query2(int x,int l,int r,int ql,int qr,int qmid,ll res)
{
if (l==r)
{
if (res+(l-qmid+1)*a[qmid]<val[x]+(qmid-ql+1)*a[qmid]) return l+1;
return l;
}
pushdown(x,l,r);
int mid=(l+r)>>1;
if (mid<qmid) return query2(x*2+1,mid+1,r,ql,qr,qmid,res);
if (mid>qr) return query2(x*2,l,mid,ql,qr,qmid,res);
ll v1=res+(mid-qmid+1)*a[qmid];
ll v2=val[x*2]+(qmid-ql+1)*a[qmid];
if (v1>=v2) return query2(x*2,l,mid,ql,qr,qmid,res);
else return query2(x*2+1,mid+1,r,ql,qr,qmid,res);
}
}seg;
void solve(int l,int r)
{
if (l>r) return;
int mid=findmax(l,r);
if (l==r) seg.update2(1,1,n,l,r,a[l],0);
else
{
solve(l,mid-1); solve(mid+1,r);
ll res=seg.query1(1,1,n,mid-1);
int p=seg.query2(1,1,n,l,r,mid,res);
seg.update1(1,1,n,mid,p-1);
seg.update2(1,1,n,mid,p-1,res+a[mid],a[mid]);
seg.update2(1,1,n,p,r,a[mid]*(mid-l+1),0);
}
for (int i=0;i<ask[mid].size();i++)
{
int j=ask[mid][i];
b[j].f[1]=seg.query1(1,1,n,b[j].r);
}
}
void work()
{
buildst();
seg.update1(1,1,n,1,n);
for (int i=1;i<=n;i++) ask[i].clear();
for (int i=1;i<=Q;i++)
{
int p=findmax(b[i].l,b[i].r);
if (p==b[i].r) continue;
ask[findmax(p+1,b[i].r)].push_back(i);
}
solve(1,n);
}
void rev()
{
reverse(a+1,a+1+n);
for (int i=1;i<=Q;i++)
{
swap(b[i].l,b[i].r); swap(b[i].f[0],b[i].f[1]);
b[i].l=n-b[i].l+1; b[i].r=n-b[i].r+1;
}
}
signed main()
{
scanf("%lld%lld",&n,&Q);
for (int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for (int i=1;i<=Q;i++)
{
scanf("%lld%lld",&b[i].l,&b[i].r);
b[i].l++; b[i].r++;
}
ID=0; work(); rev();
ID=1; work(); rev();
ID=0; buildst();
for (int i=1;i<=Q;i++)
{
ll f0=b[i].f[0],f1=b[i].f[1];
int l=b[i].l,r=b[i].r,mid=findmax(l,r);
cout<<min(f0+(r-mid+1)*a[mid],f1+(mid-l+1)*a[mid])<<"
";
}
return 0;
}