【洛谷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\),那么有

\[f[l][r]=\min(f[l][mid-1]+a[mid]\times (r-mid+1),f[mid+1][r]+a[mid]\times (mid-l+1)) \]

也就是考虑是最大值左边的人去右边还是右边的人去左边。
我们发现每次转移都和区间里的最大值 \(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;
}
posted @ 2021-07-16 07:49  stoorz  阅读(59)  评论(0编辑  收藏  举报