[斜率優化DP] codeforces 673E. Levels and Regions

題意:
要把1n 分成k 組,每組內的數必須連續,組與組不相交且每個數必須屬於一個組,並且任意i 有一個參數ti
如果[l,r] 爲一組,那麼從l 走到l+1 的概率是tltl ,從l+1 走到l+2 的概率是tltl+1tl+1tl+1 ,依次類推,從l 要麼走到l+1 ,要麼原地不動,那麼組[l,r] 的費用就是從l 走到r 的期望次數。現在要分成k 組,讓總費用最小,每個數僅能屬於一個組。
題解:
先推期望公式。
sum[i]=ij=1tirev[i]=ij=11ti ,那麼從1 走到i 的期望exc[i]=exc[i1]+sum[i]ti
然後觀察得到從l 走到r 的期望公式exc[l][r]exc[r]exc[l1]sum[l1](rev[r]rev[l1])
然後設dp[i][j] 表示以前i 個數成j 段的最小費用。
轉移:dp[i][j]=min(dp[l][j1]+exc[l+1][i])
發現這是個O(n2k) 的轉移。
當我們的i 固定的時候,就是要在[1,i1] 找一個最優解l ,觀察形式得出可以斜率優化,O(nk) 的複雜度就可以過了。
斜率優化可以看這裏
推出斜率公式爲

gp(j,k)=y(j,p)y(k,p)sum[j]sum[k]

其中y(x,p)=dp[x][p]exc[x1]+sum[x1]rev[x1]
並且推出結論k<j<ig(j,k)<rev[i] ,那麼對於i 來說,j 要優於k
由於rev[i]i 遞增而遞增,當求解l 的時候,無需二分,只需要維護單調隊列的top最優就可以了。
代碼:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
double exc[N] = {0}, sum[N] = {0}, rev[N] = {0};
double t[N];
double dp[N][55];
inline double y(int x, int p){
    return dp[x][p] - exc[x] + sum[x]*rev[x];
}
inline double g(int j, int k, int p){
    return (y(j, p)-y(k, p))/(sum[j]-sum[k]);
}
inline double cal(int l, int i){
    return exc[i]-exc[l]-(rev[i]-rev[l])*sum[l];
}
int q[N], top, tail;
inline void add(int i, int k){
    while(top < tail-1 && g(i, q[tail-1], k) < g(q[tail-1], q[tail-2], k)) tail--;
    q[tail++] = i;
}
inline int getmax(int i, int k){
    while(top < tail-1 && g(q[top+1], q[top], k) < rev[i]) top++;
    return q[top];
}
int main(){
    int n, K;
    scanf("%d%d", &n, &K);
    for(int i = 1; i <= n; ++i) scanf("%lf", t+i);
    for(int i = 1; i <= n; ++i){
        sum[i] = sum[i-1] + t[i];
        rev[i] = rev[i-1] + 1.0/t[i];
        exc[i] = exc[i-1] + sum[i]/t[i];
    }
    for(int i = 1; i <= n; ++i) dp[i][1] = exc[i];
    for(int k = 2; k <= K; ++k){
        top = tail = 0;
        q[tail++] = 0;
        for(int i = 1; i <= n; ++i) add(i, k-1);
        for(int i = 1; i <= n; ++i){
            if(i >= k){
                int l = getmax(i, k-1);
                dp[i][k] = dp[l][k-1] + cal(l, i);
            }
            else dp[i][k] = 1e50; //不可能狀態
        }
    }
    printf("%.10f\n", dp[n][K]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章