【洛谷P5044】meetings 会议
题目
题目链接:https://www.luogu.com.cn/problem/P5044
有 \(N\) 座山横着排成一行,从左到右编号为从 \(0\) 到 \(N-1\)。山的高度为 \(H_i\)(\(0\leq i\leq N-1\))。每座山的顶上恰好住着一个人。
你打算举行 \(Q\) 个会议,编号为从 \(0\) 到 \(Q-1\)。会议 \(j\)(\(0\leq j\leq Q-1\)) 的参加者为住在从山 \(L_j\) 到山 \(R_j\)(包括 \(L_j\) 和 \(R_j\))上的人(\(0\leq L_j\leq R_j\leq N-1\))。对于该会议,你必须选择某个山 \(x\) 做为会议举办地(\(L_j\leq x\leq R_j\))。举办该会议的成本与你的选择有关,其计算方式如下:
- 来自每座山 \(y\)(\(L_j\leq y\leq R_j\)) 的参会者的成本,等于在山 \(x\) 和 \(y\) 之间(包含 \(x\) 和 \(y\))的所有山的最大高度。特别地,来自山 \(x\) 的参会者的成本是 \(H_x\),也就是山 \(x\) 的高度。
- 会议的成本等于其所有参会者的成本之和。
你想要用最低的成本来举办每个会议。
注意,所有的参会者将在每次会议后回到他们自己的山;所以一个会议的成本不会受到先前会议的影响。
\(n,Q\leq 7.5\times 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])<<"\n";
}
return 0;
}