斜率优化DP初探 + 例题

来源:互联网 发布:每股收益的算法 编辑:程序博客网 时间:2024/06/05 16:20

斜率优化DP学得我真的好痛苦。。。。。。。
太菜了太菜了。。
由于才刚刚接触斜率DP,所以讲不清楚,主要是给自己看,加深印象。。 以后有机会会优化一下。

PART. 1 斜率优化是啥?


斜率优化,利用的是数形结合的思想,给纯代数问题一个合理的几何解释,利用图形的直观性,直接挑选出最优解(剔除不必要的决策即剪枝)

PART. 2 啥时候可以用斜率优化?


斜率优化DP主要针对于具有

DP[i]=A*DP[F(k)]+B*cost[f(k)]; (A,B为常数)

这种形式的式子,当数据范围很大时,普通区间DPO(n^3)写法肯定是会超时的,这个时候应该往斜率优化或四边形优化上去想。(要考虑决策单调性,因为斜率优化靠单调队列来维护,所以我们要保证如果有k1>k2时,若k1优于k2,则k2永远不会成为状态转移的最优解)
如何证明单调性。。 这个问题很玄学。

PART. 3 使用斜率优化的基本步骤是啥?


我们假设k1是DP[i]=min(DP[F(k)]+cost[f(k)]);这个式子的最优解,那么 肯定有对于任意的k2 都满足 DP[F(k1)]+cost[f(k1)]<=DP[F(k2)]+cost[f(k2)] 我们可以把这个不等式化为斜率的形式
既( DP[F(k1)] - DP[F(k2)] )/( cost[f(k1)] - cost[f(k2)] )<=或>=-1;
(注意,斜率优化一般保证cost元素单调)。这样,数式问题就有了几何解释。 一般来说,斜率优化可以达到降维的效果。
所以 对于任意k1,k2. 如果k1,k2满足上面的不等式,那么对于当前状态转移式来说,k1要比k2优(由于决策单调性,这时候的k2可以被踢出队列,因为他永远不会成为最优解)
我们怎么样来维护这些个有可能成为最优解的k值呢。
因为他们是符合决策单调性的,所以我们可以使用单调队列来维护,每次决策时,取出单调队列的元素进行比较 如果对于当前状态转移来说,后一个优于前一个 那么前一个元素就可以被踢出队列。
这样优化还不够,我们再来结合图形分析一下,假设队列中有3个点h1,h2.我们现在要往队列中压入h3点(他们肯定是单调的) 。设K(a,b)为两点之间的斜率。假设我们的状态转移判断式是

( DP[F(k1)] - DP[F(k2)] )/( cost[f(k1)] - cost[f(k2)] )<=A(A为常数)

那么如果K(h1,h2)>K(h2,h3),h2肯定不会成为最优解, 因为K(h1,h2)都<=A了 ;K(h2,h3)肯定也满足条件 所以我肯可以把h2点踢出队列。

PART 4 如何保证斜率优化的正确性?


水平有限,目前没有做到看似能用斜率优化但却不满足决策单调性的问题。
一般来说,要用斜率优化的题都是套路题。
如果真的要证明决策单调性,我们一般是写一个纯暴力的写法打表解决。
下面一一道例题来说明。
CodeForces 311B/CSU 1963 Cats Transport/Feed The Rabbit
题目链接:http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1963
这道题其实也是个裸的斜率优化题目。题解这里就盗用一下以为国防科大菊苣的题解:
http://blog.csdn.net/u013534123/article/details/75837296#comments
这篇题解讲的很详细,但是有一个点我刚开始看的时候一直没搞清。
就是这句话:还是一样,假设决策k优于决策l*(k>l)*
为什么最优决策k一定大于决策l能。 我去问了这位菊苣,他说这是假设ORZ。。。。。 不太懂为什么这么假设就一定能正确。没敢继续问下去。 所以我选择了打表( ヽ( ̄▽ ̄)ノ)
直接套用这位菊苣的代码,把dp部分改成n^3的 并输出每个决策

sort(a+1,a+1+m);    for(int i=1;i<=m;i++)        s[i]=s[i-1]+a[i];    memset(dp,0x3f,sizeof dp);    dp[0][0]=0;    for(i=1;i<=p;i++)    {        for(j=i;j<=m;j++)        {            printf("dp[%d][%d]: \n",i,j);            for(int k=0;k<=j;k++)            {                printf("k:%d :%d\n",k,dp[i-1][k]+a[j]*(j-k)-s[j]+s[k]);                dp[i][j]=min(dp[i][j],dp[i-1][k]+a[j]*(j-k)-s[j]+s[k]);            }        }    }

输入样例:

4 6 2 1 3 5 1 0 2 1 4 9 1 10 2 10 3 12

得到输出
这里写图片描述
————————————————————————————————————
先写这么多,以后有新的理解了再进行补充
例题
http://acm.hdu.edu.cn/showproblem.php?pid=3480

题意:
给定一个大小为n的集合,要求将集合分成m个子集合,每个集合都有权值,权值为最大值减最小值的平方。

思路: 我们先贪心排波序。然后就是裸的区间DP了。

#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>#include<vector>#include<queue>#include<cmath>#include<limits>using namespace std;#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1#define fuck(x) cout<<x<<endl#define mem(a,b) memset(a,b,sizeof a)const int maxn=1e4+5;const int mod=1e9+7;#define INT_MAX 2147483647int a[maxn];// 第i个物品的至int q[maxn];int dp[2][maxn];int getdp(int per,int j,int k){     return dp[per][k]+(a[k+1]-a[j])*(a[k+1]-a[j]);}int Y(int per,int k){    return dp[per][k]+a[k+1]*a[k+1];}int X(int k){    return a[k+1];}bool check(int j,int per,int k1,int k2){    return 2*a[j]*(X(k2)-X(k1))>=Y(per,k2)-Y(per,k1);}bool pushjudge(int per,int i,int j,int k){    return ((Y(per,j)-Y(per,i))*(X(k)-X(j))>=(Y(per,k)-Y(per,j))*(X(j)-X(i)));}int main(){    int T;    scanf("%d",&T);    int iCase=0;    while(T--)    {        int m,n;        scanf("%d %d",&n,&m);        for(int i=1;i<=n;i++)            scanf("%d",&a[i]);        sort(a+1,a+n+1);        int h=0,t=0;        memset(dp,0x3f,sizeof dp);        for(int i=1;i<=n;i++)            dp[0][i]=(a[1]-a[i])*(a[1]-a[i]);        int turn=1;        for(int i=2;i<=m;i++)        {            h=t=0;            q[t]=i-1;            int now=turn;            int per=turn^1;            for(int j=i;j<=n;j++)            {                while(h<t&&check(j, per,q[h],q[h+1]))h++;                dp[now][j]=getdp(per,j,q[h]);                while(h<t&&pushjudge(per,q[t-1],q[t],j)) t--;                q[++t]=j;            }            turn=per;        }        printf("Case %d: %d\n",++iCase,dp[turn^1][n]);    }}