斜率优化 hdu3480 pku3709 pku1180 pku2180

来源:互联网 发布:淘宝上海家化有假的吗 编辑:程序博客网 时间:2024/05/16 05:31

      斜率优化是DP优化的一种,假设状态转移方程为dp[i]=min or max (dp[k]+w[i,k]),我们假设取其中两个解k1,k2(不妨设k1<k2),然后得到dp[k1]+w[i,k1]-(dp[k2]+w[i,k2])这个表达式,不妨设结果想取最小值的话,那么上面那个表达式<0就说明k1比k2优。接着经过将左边的一些对i有关的量移到小于号右边,然后就会发现右边的式子在k1<k2时对i具有单调性(如果单调递增的话,i就从1求到n,如果递减,i就从n求到1,状态转移方程变一下)。若此时k2比k1优,那么当求后面的i时,k2一定会比k1优的(因为有单调性)。所以如果我们求dp[i]时的最佳值为kk,那么取下一个i的最优值时,kk前删除的就不用考虑了。

      但是对i的单调性是建立在k1<k2的基础上的,所以如果问题比较极端的话,每次都是最小的最佳,那么将一点改善都没有,所以我们还要寻求别的改善方法。对上面移项后的式子,把右边除i后的项全部除到左边(此时注意符号的变化)。把此时左边的所有项记为G(k1,k2),如果k1<k2且G(k1,k2)<f[i](这是假设上面符号没变,符号变了后可以自己推下)时,k1比k2优.如果存在k1<k2<k3,使得G(k1,k2)<=G(k2,k3)时,k2永远不会是最佳的。因为当G(k1,k2)<=f[ii]时,k1会比k2,当G(k1,k2)>f[ii]时,G(k2,k3)也会>f[ii],此时k3会比k2优.所以此时可以把k2去掉,去掉后G(k1,k2)会保持单调递减,所以把此时的f[ii]插入到G(k1,k2)中,前面的G(k1,k2)都会>=f[ii],此时k2会比k1优,后面的都是k1比k2优,所以插入点的那个位置就可以知道最优的kk.具体的实现用两端都能出的队列即可完成。因为每个点进一次出一次队列,所以时间复杂度为O(n)。下面以hdu3480详细解释一下过程.

      hdu3480求将一个大集合分为m个小集合,使每个集合最大元素减最小元素平方和最小.也可用四边形不等式。排序后,状态转移方程为dp[i][j]=min(dp[k][j-1]+(a[i]-a[k+1])^2),然后k1<k2并且满足dp[k2][j-1]-dp[k1][j-1]+a[k2+1]*a[k2+1]-a[k1+1]*a[k1+1]<2*a[i]*(a[k2+1]-a[k1+1])时,k2会比k1优,所以把k1去掉。把右边的2*(a[k2+1]-a[k1+1])除到左边去(此时注意除的数的正负),然后把整体定义为一个函数G(k1,k2),如果G(k1,k2)<a[i]时,k2会比k1优。如果现在存在k1<k2<k3&&G(k2,k3)<G(k1,k2)时,那么k2是多余的,它永远也不会取到最优值(分别对G(k2,k3)<?a[i]讨论)。所以直接把k2去掉.

#include<iostream>#include<algorithm>using namespace std;int dp[10010][5010],a[10010],q[10010];int main(){int n,m,i,j,k1,k2,k3,x1,y1,x2,y2,x3,y3,t,test=1,head,tail;scanf("%d",&t);while(t--){scanf("%d%d",&n,&m);for(i=0;i<n;i++)scanf("%d",&a[i]);sort(a,a+n);for(i=0;i<n;i++)dp[i][1]=(a[i]-a[0])*(a[i]-a[0]);for(j=2;j<=m;j++){head=0,tail=0;q[tail++]=j-2;for(i=j-1;i<n;i++){while(head+1<tail){k1=q[head],k2=q[head+1];if(dp[k2][j-1]-dp[k1][j-1]+a[k2+1]*a[k2+1]-a[k1+1]*a[k1+1]<2*a[i]*(a[k2+1]-a[k1+1]))head++;else break;}k1=q[head];dp[i][j]=dp[k1][j-1]+(a[i]-a[k1+1])*(a[i]-a[k1+1]);while(head+1<tail&&i!=n-1){k1=q[tail-2],k2=q[tail-1],k3=i;x1=a[k1+1],x2=a[k2+1],x3=a[k3+1];y1=dp[k1][j-1]+a[k1+1]*a[k1+1];y2=dp[k2][j-1]+a[k2+1]*a[k2+1];y3=dp[k3][j-1]+a[k3+1]*a[k3+1];if((y3-y2)*(x2-x1)<=(y2-y1)*(x3-x2))tail--;else break;}q[tail++]=i;}}printf("Case %d: %d/n",test++,dp[n-1][m]);}return 0;}

      pku3709将一个含n个数的序列分为几个分为几个至少含有k个数的小序列,每个小序列的数都变为这个小序列最小的那个,每个数改变的和最小的那种情况就是所求。

#include<iostream>using namespace std;__int64 sum[110000],dp[110000],q[110000];int main(){__int64 n,f,i,j,k,head,tail,k1,k2,k3;while(scanf("%I64d%I64d",&n,&f)!=EOF){sum[0]=0;for(i=1;i<=n;i++){scanf("%I64d",&j);sum[i]=sum[i-1]+j;}head=tail=0;q[tail++]=0;for(i=f;i<=n;i++){while(head+1<tail){k1=q[head],k2=q[head+1];if((sum[i]-sum[k1])*(i-k2)<=(sum[i]-sum[k2])*(i-k1))head++;else break;}k=q[head];dp[i]=1000*(sum[i]-sum[k])/(i-k);while(head+1<tail){k1=q[tail-2],k2=q[tail-1],k3=i-f+1;if((sum[k2]-sum[k1])*(k3-k2)>=(sum[k3]-sum[k2])*(k2-k1))tail--;else break;}q[tail++]=i-f+1;}for(k=0,i=f;i<=n;i++)if(dp[i]>k)k=dp[i];printf("%I64d/n",k);}return 0;}

       pku1180

//将任务划分为不同的集合,同一个集合的任务在一个机器上运行//同一集合的任务都有一个完成时间,但所有的输出时间都一样,//等于前面集合的运行时间+开机时间S+这一集合上所有任务完成所需时间//每个任务都有一个花销因子Fi,问怎么划分使得每个任务的Fi*输出时间和最少//状态转移方程为:f[i]=min(f[k]+(ST[i]-ST[k]+S)*(F[n]-F[k]));#include<iostream>//#include "stdafx.h" using namespace std;//#define int L//#define __int64  LLconst __int64 maxn=11000;__int64 n,S,f[maxn],q[maxn],T[maxn],F[maxn],ST[maxn],SF[maxn];__int64 C(__int64 k,__int64 i){return f[k]+(ST[i]-ST[k]+S)*(SF[n]-SF[k]);}int main(){__int64 i,head,tail,k1,k2,k3,x1,x2,x3,y1,y2,y3;while(scanf("%I64d%I64d",&n,&S)!=EOF){for(i=1;i<=n;i++)scanf("%I64d%I64d",&T[i],&F[i]);ST[0]=SF[0]=0;for(i=1;i<=n;i++){ST[i]=ST[i-1]+T[i];SF[i]=SF[i-1]+F[i];}head=tail=0;q[tail++]=0;f[0]=0;for(i=1;i<=n;i++){while(head+1<tail){k1=q[head],k2=q[head+1];if(C(k2,i)<=C(k1,i))head++;else break;}f[i]=C(q[head],i);while(head+1<tail){k1=q[tail-2],k2=q[tail-1],k3=i;x1=SF[k1],x2=SF[k2],x3=SF[k3];y1=f[k1]-ST[k1]*(SF[n]-SF[k1]);y2=f[k2]-ST[k2]*(SF[n]-SF[k2]);y3=f[k3]-ST[k3]*(SF[n]-SF[k3]);if((y2-y1)*(x3-x2)>=(y3-y2)*(x2-x1))tail--;else break;}q[tail++]=i;}printf("%I64d/n",f[n]);}return 0;}

      斜率优化与四边形不等式的区别在于四边形不等式满足k续单调,而斜率只是保证取最优值的k单调。