dp优化学习笔记

斜率优化

原谅我之前做斜率优化题的时候都是在瞎bb,这才是靠谱的

比如我们有个这样的式子,要求最大化 Y = f [ i ] = A ( i ) + B ( j ) + C ( i ) D ( j ) Y=f[i]=A(i)+B(j)+C(i)D(j) Y=f[i]=A(i)+B(j)+C(i)D(j)

我们可以先忽略 A ( i ) A(i) A(i)

Y = C ( i ) D ( j ) + B ( j ) Y=C(i)D(j)+B(j) Y=C(i)D(j)+B(j)

相当于很多直线,斜率为 D ( j ) D(j) D(j),截距为 B ( j ) B(j) B(j),求 x = C ( i ) x=C(i) x=C(i)时的最大值,这是一个半平面交的问题。由于半平面交和凸包是对偶问题,所以我们考虑转化为凸包问题。

− C ( i ) D ( j ) + Y = B ( j ) -C(i)D(j)+Y=B(j) C(i)D(j)+Y=B(j)

相当于有很多点 ( D ( j ) , B ( j ) ) (D(j),B(j)) (D(j),B(j)),给定斜率为 − C ( i ) -C(i) C(i),则要求最大化截距。显然最优的点必定是在上凸壳上,而根据一些初中数学知识,我们可以知道当这个斜率被某个点两边斜率夹在中间时为最优的点。

如果不单调的话在凸壳上二分即可。而如果 − C ( i ) -C(i) C(i)是单调的,我们可以单调队列/单调栈。

最小化同理。

Problem

BZOJ4709

Solution

这题有个性质,对于选定的区间 [ L , R ] [L,R] [L,R]选定的 s s s必定是等于左右端的贝壳大小的,因为如果不是的话,显然多余的长度不会做任何贡献,我们把端点缩减是更好的。

f [ i ] = max ⁡ a [ j ] = a [ i ] ( f [ j − 1 ] + a [ j ] ∗ ( c i − c j + 1 ) 2 ) f[i]=\max_{a[j]=a[i]}(f[j-1]+a[j]*(c_i-c_j+1)^2) f[i]=a[j]=a[i]max(f[j1]+a[j](cicj+1)2)

f [ i ] = max ⁡ a [ j ] = a [ i ] ( f [ j − 1 ] + a i c i 2 + 2 a i c i + a i + a j c j 2 − 2 a j c j − 2 a i c i c j ) f[i]=\max_{a[j]=a[i]}(f[j-1]+a_ic_i^2+2a_ic_i+a_i+a_jc_j^2-2a_jc_j-2a_ic_ic_j) f[i]=a[j]=a[i]max(f[j1]+aici2+2aici+ai+ajcj22ajcj2aicicj)

我们把各式一一对应一下,注意 a i = a j a_i=a_j ai=aj,所以可以变化一下:

A ( i ) = a i c i 2 + 2 a i c i + a i A(i)=a_ic_i^2+2a_ic_i+a_i A(i)=aici2+2aici+ai

B ( j ) = a j c j 2 − 2 a j c j + f [ j − 1 ] B(j)=a_jc_j^2-2a_jc_j+f[j-1] B(j)=ajcj22ajcj+f[j1]

C ( i ) = − 2 a i c i C(i)=-2a_ic_i C(i)=2aici

D ( j ) = c j D(j)=c_j D(j)=cj

然后只需要对于每个 a i a_i ai维护一个凸包,则斜率是单调递增的,用单调栈搞一搞即可。

时间复杂度 O ( n ) O(n) O(n)

Code

#include <cstdio>
#include <vector>
#define rg register
using namespace std;
typedef long long ll;
const int maxn=100010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
int n,x,a[maxn],c[maxn],vis[10010];
ll now,f[maxn];
vector<int> stk[10010];
ll B(int i){return (ll)a[i]*c[i]*c[i]-2ll*a[i]*c[i]+f[i-1];}
double slope(int i,int j){return (B(j)-B(i))*1.0/(c[j]-c[i]);}
int main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
	#endif
	read(n);
	for(rg int i=1;i<=n;i++)
	{
		read(a[i]);
		c[i]=++vis[a[i]];
	}
	for(rg int i=1;i<=n;i++)
	{
		int top=stk[a[i]].size()-1;
		now=2ll*a[i]*c[i];
		while(top>0&&slope(stk[a[i]][top-1],stk[a[i]][top])<slope(stk[a[i]][top],i))
		{
			stk[a[i]].pop_back();
			--top;
		}
		++top;
		stk[a[i]].push_back(i);
		while(top>0&&slope(stk[a[i]][top-1],stk[a[i]][top])<now)
		{
			stk[a[i]].pop_back();
			--top;
		}
		x=stk[a[i]][top];
		f[i]=f[x-1]+(ll)a[i]*(c[i]-c[x]+1)*(c[i]-c[x]+1);
	}
	printf("%lld\n",f[n]);
	return 0;
}

决策单调性优化

适用一些形如这样的式子 f [ i ] = min ⁡ ( f [ j ] + w ( j + 1 , i ) ) f[i]=\min (f[j]+w(j+1,i)) f[i]=min(f[j]+w(j+1,i)),且费用的计算代价较小。
如果满足 w ( i , j + 1 ) − w ( i , j ) ≥ w ( i + 1 , j + 1 ) − w ( i + 1 , j ) w(i,j+1)-w(i,j)\geq w(i+1,j+1)-w(i+1,j) w(i,j+1)w(i,j)w(i+1,j+1)w(i+1,j),那么就具有决策单调性,即 ∀ i &lt; j b e s t ( i ) ≤ b e s t ( j ) \forall_{i&lt;j}best(i)\leq best(j) i<jbest(i)best(j)

这个不等式的意义可以认为是转移位置越大,费用的增加量是不升的。所以当决策点为 j j j时,对于所有的 k &lt; j k&lt;j k<j,它的增加的费用是越来越大的,肯定不会比j更优。

如果最优决策点是单调的,我们可以存下某些点以它为最优决策点的区间,不妨称为有效区间,很显然这可以覆盖整个区间。然后我们拿队列把它全都存起来即可。转移i时,我们就可以检测一下队首是否需要弹出。

然后如果从i转移第n项更优,那么就可以将这个决策点入队,不妨设队尾元素的有效区间为 [ L , n ] [L,n] [L,n],有两种情况

  • 如果从i转移L更优,那么直接弹出队尾元素即可,继续检测
  • 否则,在 [ L , n ] [L,n] [L,n]区间内二分出分割点,然后把队尾的有效区间拆开,入队

注意如果费用计算复杂度高,可以考虑换用分治的形式来优化。

Problem

BZOJ2216

Solution

对于i,我们可以把j分为 j ≤ i j\leq i ji j ≥ i j\geq i ji两种情况进行dp。然后dp两次取最大即可。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

Code

#include <algorithm>
#include <cstdio>
#include <cmath>
#include <queue>
#define rg register
using namespace std;
typedef long long ll;
const int maxn=500010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
struct data{
	int l,r,id;
	data(const int _l,const int _r,const int _id){l=_l;r=_r;id=_id;}
};
int n,a[maxn];
double sqr[maxn],f[maxn],g[maxn];
deque<data> q;
double calc(int i,int j){return a[j]-a[i]+sqr[i-j];}
void dp(double *f)
{
	while(!q.empty()) q.pop_front();
	int l,r,mid,tmp;
	q.push_back(data(1,n,1));
	for(rg int i=2;i<=n;i++)
	{
		++q.front().l;
		while(q.front().l>q.front().r) q.pop_front();
		if(q.empty()||(calc(n,q.back().id)<calc(n,i)))
		{
			while(!q.empty()&&calc(q.back().l,q.back().id)<=calc(q.back().l,i))
			  q.pop_back();
			if(q.empty()) q.push_back(data(i,n,i));
			else
			{
				l=q.back().l;r=q.back().r;
				while(l<=r)
				{
					mid=(l+r)>>1;
					if(calc(mid,q.back().id)<calc(mid,i)) tmp=mid,r=mid-1;
					else l=mid+1;
				}
				q.back().r=tmp-1;
				q.push_back(data(tmp,n,i));
			}
		}
		f[i]=calc(i,q.front().id);
	}
}
int main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
	#endif
	read(n);
	for(rg int i=1;i<=n;i++){read(a[i]);sqr[i]=sqrt((double)i);}
	dp(f);
	reverse(a+1,a+n+1);
	dp(g);
	for(rg int i=1;i<=n;i++)
	  printf("%lld\n",max((ll)ceil(f[i]),(ll)ceil(g[n-i+1])));
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值