斜率优化 dp

P6047 丝之割

题目大意

给出 \(m\) 个二元组 \((u_i,v_i)\),现在可以选定 \(i,j\in [1,n]\) 然后花费 \(a_i\times b_j\) 的代价将所有满足 \(u>i,v<j\) 的二元组破坏掉,问破坏所有二元组的最小代价,保证 \(n,m\leq 3\times 10^5,u\in(1,n],v\in [1,n)\)。

Solution

首先注意到一个性质:

如果存在两个交叉的二元组,即 \(u_i\leq u_j,v_i\geq v_j\),那么当我们破坏 \((u_i,v_i)\)时,\((u_j,v_j)\) 也会被一起破坏,因此可以直接去掉所有交叉的二元组。

现在开始 \(dp\),我们先将所有二元组按 \(u\) 排序,此时 \(v\) 也是,设 \(dp_i\) 表示前 \(i\) 个二元组都被破坏的最小代价,那么就容易想到记录 \(a\) 的前缀 \(min\),和 \(b\) 的后缀 \(min\) 转移方程:

\[dp_i=\min_{\\j<i}\{ dp_j +mina_{u_j -1}\times minb_{v_i+1} \} \]

发现可以斜率优化,转换一下:

\[dp_i=dp_j +mina_{u_j -1}\times minb_{v_i+1} \]

\[dp_j=-mina_{u_j -1}\times minb_{v_i+1}+dp_i \]

就相当于多个点 \((-mina_{u_j-1},dp_j)\),然后用斜率为 \(minb_{v_i+1}\) 的直线截取。

观察到 \(-mina_{u_j-1}\) 随 \(j\) 增大而增大,因此直接用单调栈维护下凸包。

且 \(minb_{v_i+1}\) 随 \(i\) 增大而增大,因此可以直接用双指针维护。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>

using namespace std;

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	vector<long long>a(n+1);
	vector<long long>b(n+1);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&b[i]);
	}
	vector<pair<int,int>>seg(m+1);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&seg[i].first,&seg[i].second);
	}
	sort(seg.begin()+1,seg.end());
	vector<int>u(m+1,0);
	vector<int>v(m+1,0);
	int tot=1;
	for(int i=1;i<=m;i++)
	{
		if(seg[i].second<=v[tot-1])
		{
			continue;
		}
		u[tot]=seg[i].first;
		v[tot]=seg[i].second;
		tot++;
	}
	vector<long long>min_a(n+2,0);
	vector<long long>min_b(n+2,0);
	min_a[0]=1e18;
	min_b[n+1]=1e18;
	for(int i=1;i<=n;i++)
	{
		min_a[i]=min(min_a[i-1],a[i]);
	}
	for(int i=n;i>=1;i--)
	{
		min_b[i]=min(min_b[i+1],b[i]);
	}
	vector<long long>dp(tot+1,1e18);
	dp[0]=0;
	vector<pair<long long,long long>>stk(tot+5);
	int s=1,t=1;
	stk[1]=make_pair(-min_a[u[1]-1],0);
	auto slope=[](pair<long long,long long>a,pair<long long,long long>b)
	{
		if(a.first==b.first)
		{
			return 1e18l;
		}
		return (long double)(b.second-a.second)/(long double)(b.first-a.first);
	};
	for(int i=1;i<tot;i++)
	{
		while(s<t&&(long double)(min_b[v[i]+1])>slope(stk[s],stk[s+1]))
		{
			s++;
		}
		dp[i]=stk[s].second-stk[s].first*min_b[v[i]+1];
		if(i!=tot-1)
		{
			while(s<t&&slope(stk[t-1],stk[t])>slope(stk[t],make_pair(-min_a[u[i+1]-1],dp[i])))
			{
				t--;
			}
			stk[++t]=make_pair(-min_a[u[i+1]-1],dp[i]);
		}
	}
	cout<<dp[tot-1]<<endl;
	return 0;
}
posted @ 2026-10-09 17:19  will111  阅读(3)  评论(0)    收藏  举报