• 【洛谷P5044】meetings 会议


    题目

    题目链接: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),那么有

    [f[l][r]=min(f[l][mid-1]+a[mid] imes (r-mid+1),f[mid+1][r]+a[mid] imes (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])<<"
    ";
    	}
    	return 0;
    }
    
  • 相关阅读:
    线性回归损失函数求解
    【线性代数】四个基本子空间
    【线性代数】如何寻找一个投影矩阵
    【hihoCoder】#1133 : 二分·二分查找之k小数
    [LeetCode解题报告] 502. IPO
    [LeetCode解题报告] 703. 数据流中的第K大元素
    【排序】堆排序
    全文检索以及Lucene的应用
    MySql优化之mycat
    MySql优化之主从复制
  • 原文地址:https://www.cnblogs.com/stoorz/p/15018386.html
Copyright © 2020-2023  润新知