[Codeforces 673E] Levels and Regions (期望+斜率DP)

Codeforces - 673E

N 个关卡,可以分为 K块,每个关卡都有个权值 ti
每次选择最早没有通关的关卡块,设这个关卡包含了 [i,j] 的游戏
选到最早没有通关的关卡是 k , 选到 k的概率是 P=tkjx=ix
选到一个关卡一定能通关,花费一小时
求合理分块的情况下,通关所有关卡块的期望时间最小是多少


几个月前做过一次这道题,当时比较弱,不会推期望公式
很显然地能得到一个dp转移方程,设 dp[k][i] 为前 i 位分成 k块之后的最小期望
dp[k][i]=min(dp[k1][j]+E(j+1,i))
其中 E(j+1,i) [j+1,i] 这一块的通关期望

先研究 E(l,r) 的表达式,显然我们有
E(l,i1)=pE(l,i)+(1p)E(l,i1)+1
其中 p 为开启新的关卡的概率, p=tiix=lx
所以 E(l,i1)=E(l,i)+1p ,这样一来就可以得到
E(l,r)=i=lrij=ltjti

为了能 (1) 得到 E(l,r) ,我们对这个式子变形一下,并预处理出一些前缀和
S[i] 表示 ti 的前缀和, A[i] 表示 S[i]ti 的前缀和, B[i] 表示 1ti 的前缀和
E(l,r)=i=lrS[i]S[l1]ti
=i=lrS[i]tiS[l1]×i=lr1ti
=A[r]A[l1]S[l1]×(B[r]B[l1])

所以 dp[k][i]=min(dp[k1][j]+A[i]A[j]S[j]×(B[i]B[j]))
至此我们得到了完整的转移方程,时间复杂度为 (N2K)

但是这个复杂度显然不能通过 N2×105 的数据,所以考虑优化(斜率优化)
斜率优化本质上就是一个利用单调队列维护下凸壳的过程,
一些形如 dp[i]=min(dp[j]+W(j,i)) 的方程都可能用得上这个优化

设下标 k<j ,并且 dp[k]+E(k+1,i)>dp[j]+E(j+1,i) ,那么显然我们选择从 j 转移
展开这个式子,能得到
dp[k]+A[i]A[k]S[k]×(B[i]B[k])>dp[j]+A[i]A[j]S[j]×(B[i]B[j])
化简成如下形式
(dp[j]A[j]+S[j]×B[j])(dp[k]A[k]+S[k]×B[k])S[j]S[k]<B[i]
左边就变成了一个斜率的形式,而右边的 B[i] 是单调递增的
也就是说所有斜率小于 B[i] k 都能被优化掉,
而优化掉之后剩下能转移的点就形成了一个下凸壳
利用单调队列维护这个凸壳,每个点最多只会进队出队一次
这样一来时间复杂度就是 (NK)

#pragma comment(linker, "/STACK:102400000,102400000")
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>
#include <map>
#include <set>
#include <queue>
#include <bitset>
#include <string>
#include <complex>
using namespace std;
typedef pair<int,int> Pii;
typedef long long LL;
typedef unsigned long long ULL;
typedef double DBL;
typedef long double LDBL;
#define MST(a,b) memset(a,b,sizeof(a))
#define CLR(a) MST(a,0)
#define SQR(a) ((a)*(a))
#define PCUT puts("\n----------")

const int maxn=2e5+10;
int N,K,T[maxn];
DBL A[maxn],B[maxn],S[maxn],dp[2][maxn];
int que[2*maxn];
DBL Y(int,int,int), X(int,int);

int main()
{
    #ifdef LOCAL
    freopen("in.txt", "r", stdin);
//  freopen("out.txt", "w", stdout);
    #endif

    while(~scanf("%d%d", &N, &K))
    {
        for(int i=1; i<=N; i++)
        {
            scanf("%d", &T[i]);
            S[i] = S[i-1] + T[i];
            A[i] = A[i-1] + S[i]/T[i];
            B[i] = B[i-1] + 1.0/T[i];
            dp[1][i] = A[i];
        }
//      for(int i=1; i<=N; i++) printf("%.4f\n", A[i]);
        for(int k=2,cur,las,head,tail; k<=K; k++)
        {
            head=0,tail=0;
            cur = k&1, las = (k-1)&1;
            que[tail++] = 0;
            CLR(dp[cur]);
            for(int i=1; i<=N; i++)
            {
                while(tail-head>1 && Y(las,que[head],que[head+1]) < X(que[head],que[head+1])*B[i]) head++;
                int p = que[head];
                dp[cur][i] = dp[las][p] + A[i] - A[p] - S[p]*(B[i]-B[p]);
                while(tail-head>1 && 
                    Y(las,que[tail-1],i)*X(que[tail-2],que[tail-1]) - Y(las,que[tail-2],que[tail-1])*X(que[tail-1],i) <= 0) tail--;
                que[tail++] = i;
            }
        }
        printf("%.6f\n", dp[K&1][N]);
    }
    return 0;
}

DBL Y(int cur, int k, int j)
{
    return (dp[cur][j]-A[j]+S[j]*B[j])-(dp[cur][k]-A[k]+S[k]*B[k]);
}

DBL X(int k, int j)
{
    return S[j]-S[k];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值