POJ 3709 斜率优化DP
此题有是方程好写,优化很难(对于神犇们,简直太水了)的一道题
建议做这道题之前先看这道题:http://www.lydsy.com/JudgeOnline/problem.php?id=1010
题意:
将一个升序的,有N个元素的序列,分组。要求每组的元素不少于K个,计算出组内各元素与最小元素的之差的和,将每组的这个值加起来,其和要最小。
思路:
由以上可得DP方程:
dp[i]=MIN(dp[j]+sum[i]-sum[j]-(i-j)*arr[j+1]); j<i-k+1
开始斜率优化(不考虑每组不少于K个元素):
1.证明较优决策点对后续状态影响的持续性
证明很简单,不证了,有兴趣的话,参考上一篇文章
2.求斜率方程:一般化为左边是J,K,右边是I的形式
假设J<K,且在K点的决策比J好,则有
dp[j]+sum[i]-sum[j]-(i-j)*arr[j+1]>= dp[k]+sum[i]-sum[k]-(i-k)*arr[k+1]
化简得:
dp[j]-dp[k]-sum[j]+sum[k]+j*arr[j+1]-k*arr[k+1]>=i* (arr[j+1]-arr[k+1])
令G(k,j)= dp[j]-dp[k]-sum[j]+sum[k]+j*arr[j+1]-k*arr[k+1]
S(k,j)= arr[j+1]-arr[k+1]
则上式化为G(k,j)>=i*S(k,j)
即G(k,j)/S(k,j)<=i 记住变号,因为S(k,j)<0
令X(k,j)= G(k,j)/S(k,j)
所以斜率方程:X(k,j)<=i
3.规定队列的维护规则
队首维护:
假设A,B(A<B)是队首元素,若X(B,A)<=i,则B比A好,删除A,否则不需维护.
队尾维护:
假设A,B,C(A<B<C)是队尾元素
a.若X(B,A)<=i,且X(C,B)<=i,则C比B好,B比A好
b.若X(B,A)<=i,且X(C,B)>i,则B比C好,B比A好,B为极大值
c.若X(B,A)>i,A比B好
a,c情况直接删掉B,b情况保留.b情况可改为X(B,A)<X(C,B)
好,以下考虑每组不少于K个元素这个限制。
要解决这个限制,只需延迟加入的时机即可。
若延迟K-1个回合加入,有可能使前一组的个数少于K个。
若延迟2*k-1个回合加入,则不会出现这情况。但此时加入的数应是i-k+1(假设是第I回合)
第一次做斜率优化DP,照着题解写的,以上内容转自:http://blog.sina.com.cn/s/blog_5f5353cc0100jxxo.html
代码按照题解思路写的:
DP真的好神奇!~
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <iostream> 5 6 #define N 505000 7 8 using namespace std; 9 10 int n,k,tt; 11 __int64 dp[N],sum[N],a[N],q[N]; 12 13 void read() 14 { 15 scanf("%d%d",&n,&k); 16 for(int i=1;i<=n;i++) 17 { 18 scanf("%I64d",&a[i]); 19 sum[i]=sum[i-1]+a[i]; 20 } 21 } 22 23 __int64 G(int y,int x) 24 { 25 return dp[x]-dp[y]-sum[x]+sum[y]+x*a[x+1]-y*a[y+1]; 26 } 27 28 __int64 S(int y,int x) 29 { 30 return a[x+1]-a[y+1]; 31 } 32 33 void go() 34 { 35 dp[0]=0; 36 int h=1,t=1; 37 q[t++]=0; 38 for(int i=1,x,y,z;i<=n;i++) 39 { 40 while(h<t-1&&G(q[h+1],q[h])>=i*S(q[h+1],q[h])) h++;//把不可能成为最优值的出队 41 42 dp[i]=dp[q[h]]+sum[i]-sum[q[h]]-(i-q[h])*a[q[h]+1]; 43 44 if(i>=2*k-1) q[t++]=i-k+1;//延迟更新 45 46 for(int j=t-2;j-1>=h;j--) 47 { 48 x=q[j-1]; y=q[j]; z=q[j+1]; 49 if(G(y,x)*S(z,y)>=G(z,y)*S(y,x)) q[j]=q[--t]; 50 else break; 51 } 52 } 53 printf("%I64d\n",dp[n]); 54 } 55 56 int main() 57 { 58 scanf("%d",&tt); 59 while(tt--) 60 { 61 read(); 62 go(); 63 } 64 return 0; 65 }