定义dp[i][j] 代表在i这个地方放一个Heap , 从1到i这个区间一共放了n个heap,最小的cost。
很容易想到dp转移方程:dp[i][j] = min { dp[k][j-1] + cost ( k ,i) } ( 1<=K<i , cost[(k,i) 代表把 k+1到i区间中的所有heap移动到第 i这个heap的cost。
这是2d/1d的dp 还有优化余地。
展开转移方程: dp[i][j] = min ( dp[k][j-1] + (p[i].h - p[i].h) * p[i].w + ( p[i].h - p[i-1].h ) * p[i-1].w + ... + (p[i].h - p[k+1].h) * p[k+1].w )
定义 sw[i] = sum(p[k].w) (1<=k<=i) ; sm[i] = sum(p[k].h * p[k].w) (1<=k<=i)
所以 dp[i][j] = min ( dp[k][j-1] + p[i].h * ( sw(i) - sw(k)) - ( sm(i) - sm(k) );
再转化一下 就是 dp[k][j-1] + sm[k] = p[i].h * sw[k] + dp[i][j] + sm[i] -sw[i]*p[i].h ;
令 y = dp[k][j-1] + sm[k] ; x = sw[k]; a = p[i].h ; b = dp[i][j] + sm[i] -sw[i]*p[i].h;
所以 y = a * x + b;
其中b就是我们间接要求的,剩下就是用栈维护一个凸包,将复杂度降为 n^2
#include <cstdio>   
#include <iostream>
#include <queue>
#include <string.h>   
#include <cmath>
#include <vector>
#include <algorithm>   
using namespace std;
typedef long long ll;
#define sfint(x) scanf("%d",&x)
#define sfint2(x,y) scanf("%d%d",&x,&y)
#define sfint3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define sfstr(c) scanf("%s",c)
#define sfdl(x) scanf("%lf",&x)
#define sfll(x) scanf("%I64d",&x)
#define sfch(c) scanf("%c",&c)
#define fr(i,s,n) for(int i=s;i<n;++i)
#define cl(a) memset(a,0,sizeof(a))
int n,K;
const ll inf = 1LL<<60;
ll dp[1010][1010];
struct STA{
	struct Sta{
		ll x,y;
		Sta(ll a,ll b):x(a),y(b){}
		Sta(){}
	}s[1010];
	int top,now;
	void init(){
		top = -1;
		now = 0;
	}
	void P(ll a,ll b){
		if(top <1) s[++top] = Sta(a,b);
		else{
			while(1){
				if(top == 0){
					s[++top] = Sta(a,b);
					break;
				}
				ll y1 = b-s[top].y;
				ll x1 = a-s[top].x;
				ll y2 = s[top].y - s[top-1].y;
				ll x2 = s[top].x - s[top-1].x;
				if( y1 * x2 > y2 * x1){
					s[++top] = Sta(a,b);
					break;
				}
				else{
					top --;
				}
			}
		}
	}
	Sta Top(){
		return s[top];
	}
}S;
struct P{
	ll h,w;
	void read(){
		scanf("%lld%lld",&h,&w);
	}
}p[1010];
ll sw[1010],sm[1010];
void init(){
	sw[0] = 0;sm[0] = 0;
	fr(i ,1  ,n+1){
		sw[i] = sw[i-1] + p[i].w;
		sm[i] = sm[i-1] + p[i].w * p[i].h;
	}
}
double cal(int x){
	return double (S.s[x+1].y - S.s[x].y)/ double(S.s[x+1].x - S.s[x].x );
}
int main(){
	while(sfint2(n,K)!=EOF){
		fr(i ,1  ,n+1){
			p[i].read();
		}
		init();
		fr(i , 0 , n+1){
			fr(j ,0 , K+1){
				dp[i][j] = inf;
			}
		}
		dp[1][1] = 0;
		S.init();
		fr(i ,2, n+1){
			dp[i][1] = dp[i-1][1] + sw[i-1] * (p[i].h - p[i-1].h);	
		}
		for(int j = 2;j<=K;++j){
			for(int i = j;i<n+1;++i){
				S.P(sw[i-1] ,dp[i-1][j-1]+sm[i-1] );
				while( S.now<S.top &&  cal(S.now) < double(p[i].h) ){
					S.now++;
				}
				dp[i][j] = S.s[S.now].y - S.s[S.now].x * p[i].h + sw[i]*p[i].h -sm[i];
				
			}
			S.init();
		}
		printf("%lld\n",dp[n][K]);
	}
	return 0;
}
 
                   
                   
                   
                   
                             本文介绍了一种使用凸包和栈来优化动态规划算法的方法,针对堆合并问题,通过定义状态转移方程并利用线性函数特性降低算法的时间复杂度。
本文介绍了一种使用凸包和栈来优化动态规划算法的方法,针对堆合并问题,通过定义状态转移方程并利用线性函数特性降低算法的时间复杂度。
           
       
           
                 
                 
                 
                 
                 
                
               
                 
                 
                 
                 
                
               
                 
                 扫一扫
扫一扫
                     
              
             
                   133
					133
					
 被折叠的  条评论
		 为什么被折叠?
被折叠的  条评论
		 为什么被折叠?
		 
		  到【灌水乐园】发言
到【灌水乐园】发言                                
		 
		 
    
   
    
   
             
            


 
            