生活随笔
收集整理的這篇文章主要介紹了
HDU 3507 斜率优化入学习
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
【題目鏈接】 點擊打開鏈接
【題意】大概題意就是要輸出N個數字a[N],輸出的時候可以連續的輸出,每連續輸出一串,它的費用是 “這串數字和的平方加上一個常數M”。
【寫在前面】 思想參考BLOG : 點擊打開鏈接
【解題方法】斜率優化, 做這個題之前還不知道什么叫斜率優化,或許是有了單調隊列的理解基礎,一看就懂了。現在把我的理解和推導寫下來,供大家交流。
首先, 比較容易想到一個很樸素的方程 : 我們設 DP[i]表示輸出到i時的 最小花費, 那么 sum[i] = 1到i的數字之和, 那么我們可以容易的列出來一個式子,
DP[i] = DP[j] + M + (sum[i] - sum[j]) ^ 2.然后顯然這是一個二維的DP。題目數據為500000, 顯然是不能通過此題的。那么考慮如何優化呢?
我們還是從上面的式子著手,我們我們假設k<j<i。如果在j的時候決策要比在k的時候決策好,那么也是就是dp[j]+M+(sum[i]-sum[j])^2<dp[k]+M+(sum[i]-sum[k])^2。
(因為是最小花費嘛,所以優就是小于)。
然后我們化簡并移項得到 : DP[j] + Sum[j] ^ 2 - (DP[k] + Sum[k] ^ 2) / (2 * (Sum[j] - Sum[k])) < Sum[i]。 我們觀察一下左邊的式子,是不是就是
(yj - yk) / (xj - xk) ,這個形式難道不就是斜率的形式嗎? 前面我們假設了 在 j的決策比在k的決策要好, 那么就是滿足 (yj - yk) / (xj - xk) < Sum[i]時,j比k決策好。
然后斜率優化最關鍵的部分來了:(先說下,下面的g[i, j]代表的是斜率)現在從左到右,還是設k<j<i,如果g[i,j]<g[j,k],那么j點便永遠不可能成為最優解,可以直接將它踢出我們的最優解集。為什么呢?
我們假設g[i,j]<sum[i],那么就是說i點要比j點優,排除j點。如果g[i,j]>=sum[i],那么j點此時是比i點要更優,但是同時g[j,k]>g[i,j]>sum[i]。這說明還有k點會比j點更優,同樣排除j點。
排除多余的點,這便是一種優化!即是點的優化,以前聽說過一個點優化不知道是不是說的斜率優化。
那么有了這個性質之后最優解如何尋找呢? 還是設k<j<i。由于我們排除了g[i,j]<g[j,k]的情況,所以整個有效點集呈現一種上凸性質,即k j的斜率要大于j i的斜率。
也即是這個圖:
這樣,從左到右,斜率之間就是單調遞減的了。當我們的最優解取得在j點的時候,那么k點不可能再取得比j點更優的解了,于是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]<sum[i],那么說明b點比a點更優,a點可以排除,于是a出隊。最后dp[i]=getDp(q[head])。
這個題雖然采用了斜率優化,但是還是有區別的在于,sum[i]并不是單調遞增的,也有可能兩個點的 x坐標是相同的,這就是為什么下面的斜率優化會用 <= 和 >= 的原因,
這個=一定不能缺,缺了欽定WA。
//
//Created by BLUEBUFF 2016/1/8
//Copyright (c) 2016 BLUEBUFF.All Rights Reserved
//#pragma comment(linker,"/STACK:102400000,102400000")
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdio>
#include <time.h>
#include <cstdlib>
#include <cstring>
#include <sstream> //isstringstream
#include <iostream>
#include <algorithm>
using namespace std;
//using namespace __gnu_pbds;
typedef long long LL;
typedef pair<int, LL> pp;
#define REP1(i, a, b) for(int i = a; i < b; i++)
#define REP2(i, a, b) for(int i = a; i <= b; i++)
#define REP3(i, a, b) for(int i = a; i >= b; i--)
#define CLR(a, b) memset(a, b, sizeof(a))
#define MP(x, y) make_pair(x,y)
const int maxn = 500010;
const int maxm = 2e5;
const int maxs = 10;
const int maxp = 1e3 + 10;
const int INF = 1e9;
const int UNF = -1e9;
const int mod = 1e9 + 7;
int gcd(int x, int y) {return y == 0 ? x : gcd(y, x % y);}
//typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>order_set;
//head//斜率優化
int n, m, head, tail, dp[maxn], q[maxn], sum[maxn], a[maxn];
int getDP(int i, int j){return dp[j] + m + (sum[i] - sum[j]) * (sum[i] - sum[j]);
}
int getUP(int j, int k){return dp[j] + sum[j] * sum[j] - (dp[k] + sum[k] * sum[k]);
}
int getDOWN(int j, int k){return 2 * (sum[j] - sum[k]);
}int main()
{while(scanf("%d%d", &n, &m) != EOF){REP2(i, 1, n) scanf("%d", &a[i]);sum[0] = dp[0] = 0;REP2(i, 1, n) sum[i] = sum[i - 1] + a[i];head = tail = 0;q[tail++] = 0;REP2(i, 1, n){while(head + 1 < tail && getUP(q[head + 1], q[head]) <= sum[i] * getDOWN(q[head + 1], q[head])) head++;dp[i] = getDP(i, q[head]);while(head + 1 < tail && getUP(i, q[tail - 1]) * getDOWN(q[tail - 1], q[tail - 2]) <= getUP(q[tail - 1], q[tail - 2]) * getDOWN(i, q[tail - 1])) tail--;q[tail++] = i;}cout << dp[n] << endl;}return 0;
}
總結
以上是生活随笔為你收集整理的HDU 3507 斜率优化入学习的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。