dp优化--斜率

给出学习的连接: 

http://www.notonlysuccess.com/index.php/dp_optimize/


hdu 2993: 

#include "cstdio"
#include "iostream"
#include "algorithm"

using namespace std;

typedef pair<int, int> point;
typedef __int64 ll;

const int N = 111111;

point poi[N];
int sum[N];

ll cross(point a, point b, point c){
    ll x0 = b.first - a.first,
    y0 = b.second - a.second,
    x1 = c.first - a.first,
    y1 = c.second - a.second;
    
    return x0*y1 - x1*y0;
}

int bsearch(int l, int r, point p){
    while(l<r){
        int mid = (l+r)>>1;
        if(cross(poi[mid], poi[mid+1], p) < 0) { r = mid; }
        else { l = mid+1; }
    }
    return l;
}

int GetInt(){
    char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    int num=0;
    while(ch>='0'&&ch<='9'){
        num=num*10+ch-'0';
        ch=getchar();
    }
    return num;
}

int main()
{
    int n, k, i, j, l, r;
    double ans, tmp;
    point curp, newp;
    
    while(~scanf("%d%d", &n, &k))
    {
        sum[0] = 0;
        for(i = 1; i <= n; ++i){
            sum[i] = GetInt();
            sum[i] += sum[i-1];
        }
        
        ans = 0;
		l = 1; 
		r = 0;
        for(i = k; i <= n; ++i){
            newp.first = i-k;
            newp.second = sum[i-k];
            curp.first = i;
            curp.second = sum[i];
            
            while(r>l && cross(poi[r-1], poi[r], newp)<0) { --r; }
            poi[++r] = newp;
		
			for(j = l; j < r && cross(poi[j], poi[j+1], curp) >= 0; ++j) ;
			l = j; // n

            
            // l = bsearch(l, r, curp); // n
			// l = bsearch(1, r, curp); // nlg(n)
            tmp = (double)(poi[l].second-sum[i])/(poi[l].first - i);
            if(tmp>ans) { ans = tmp; }
        }
        printf("%.2lf\n", ans);
    }
    
    return 0;
}


// poi可以不存

// 注意两种删点,最优值(左边),凹形函数(右边)


hdu4258:(http://serjudging.vanb.org/?p=359)


最原始的dp方程:dp[i]=min{dp[j-1]+(x[i]-x[j])^2+c} , dp[i]是i的最优值,j<=i,显然是n^2的,需要优化....

优化:假设k<j<i, 对于i来说,如果j点决策比k点好,即根据上面的方程j点算出来<k点,如下...

dp[j-1]+(x[i]-x[j])^2+c < dp[k-1]+(x[i]-x[k])^2+c...展开,化简...

dp[j-1]+x[j]^2-2*x[i]*x[j]<dp[k-1]+x[k]^2-2*x[i]*x[k]...移项...

dp[j-1]+x[j]^2-(dp[k-1]+x[k]^2)<x[i]*(2*x[j]-2*x[k])....这里将dp[j-1]+x[j]^2视为Y[j],2*x[j]视为X[j]...得到

Y[j]-Y[k]<x[i]*(X[j]-X[k])...因为X[j]-X[k]>0...可以除过去...得到...注意x和X是不同的....

(Y[j]-Y[k])/(X[j]-X[k])<x[i]....视左边的为g[j, k]...注意要保证j,k顺序,保证X[j]-X[k]>0


接下来, 关键的来了:如今从左到右,还是设k<j<i,若是g[i,j]<g[j,k],那么j点便永远不成能成为最优解,可以直接将它踢出我们的最优解集。为什么呢?

我们假设g[i,j]<x[i],那么就是说i点要比j点优,打消j点。
若是g[i,j]>=x[i],那么j点此时是比i点要更优,然则同时g[j,k]>g[i,j]>x[i]。这申明还有k点会比j点更优,同样打消j点。
打消多余的点,这便是一种优化!


于是对于这题我们对于斜率优化做法可以总结如下:
1,用一个单调队列来保护解集。
2,假设队列中从头到尾已经有元素a b c。那么当d要入队的时辰,我们保护队列的上凸性质,即若是g[d,c]<g[c,b],那么就将c点删除。直到找到g[d,x]>=g[x,y]为止,并将d点参加在该地位中。
3,求解时,从队头开端,若是已有元素a b c,当i点请求解时,若是g[b,a]<x[i],那么申明b点比a点更优,a点可以打消,于是a出队。最后dp[i]的决策就是最左边的那个点。

#include "cstdio"
#include "iostream"
using namespace std;

typedef __int64 ll;
typedef pair<ll, ll> pp;

const int N = 1111111;

ll po[N], qu[N], dp[N];


ll gg(int i, int j){
	ll yi = dp[i-1] + po[i]*po[i],
		yj = dp[j-1] + po[j]*po[j],
		xi = 2*po[i], xj=2*po[j];
	return (yi-yj)/(xi-xj);
}

ll check(pp p1, pp p2){
	return (p1.second*p2.first-p2.second*p1.first);
}


ll calc(int i, int j, int k){
	return (dp[j-1]+(po[i]-po[j])*(po[i]-po[j]))-(dp[k-1]+(po[i]-po[k])*(po[i]-po[k]));
}

ll GetLL(){  
    char ch=getchar();  
    while(ch<'0'||ch>'9') ch=getchar();  
    ll num=0;  
    while(ch>='0'&&ch<='9'){  
        num=num*10+ch-'0';  
        ch=getchar();  
    }  
    return num;  
}  

int main(){
	int i, n, c, l, r;

//	freopen("covered.in", "r", stdin);

	while(scanf("%d%d", &n, &c) && n+c)
	{
		for(i=1; i<=n; ++i)
		{
			po[i] = GetLL(); // 外挂
			// scanf("%I64d", &po[i]);
		}

		l=0; r=-1;
		for(i=1; i<=n; ++i)
		{
			dp[i]=0;
			while(l<r && gg(i, qu[r])<gg(qu[r], qu[r-1])) { 
				--r;
			}
			qu[++r]=i;

			while(l<r && calc(i, qu[l+1], qu[l])<0) {
				++l;
			}
			dp[i] = dp[qu[l]-1]+(po[i]-po[qu[l]])*(po[i]-po[qu[l]]) + c;

		//	printf("%d %d %I64d\n", i, qu[l], dp[i]);
		}
		printf("%I64d\n", dp[n]);
	}

	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值