Codeforces Round #351 (VK Cup 2016 Round 3, Div. 2 Edition) E. Levels and Regions(斜率优化dp) ★ ★ ★

本文介绍了一个关于最优分组的问题,并提出了一种基于斜率优化的动态规划算法来解决该问题,通过维护一个下凸包实现状态转移,最终达到O(n*k)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给定n个数t[1]~t[n],给定组数k,将这n个数分为k组,然后求一个期望。规则:1:同一组的元素是连续的,且组不能为空。2:每次找到第一个组(有元素未被杀死),设最小的未死元素为X,不存在则结束游戏。3:对于选定的组G和元素X,将G中杀死的元素i放t[i]个小球到抽奖箱,放t[X]个小球到抽奖箱,然后等概率的抽一个小球,这个操作花费1小时,抽到的小球代表的元素被杀死(如果是已经被杀死就跳过)。要你进行分组,使得所有元素都被杀死需要花费的时间的期望最小。

分析:首先因为同一组只能是连续的元素,我们就要会算连续的[l,r]成为一组是全部被杀死的期望时间,最基础的期望问题,设期望为F[l,r],那么会有F[l,r]=sigma(sum[i]/t[i]-sum[l-1]/t[i]),{l<=i<=r},那么我们就能设dp[i][j]表示将前j个元素分为i个组花费的最小期望。那么状态转移方程式为:dp[i][j]=min(dp[i][k]+F[k,j]),我们设Si=sigma(1<=j<=i)t[j],Qi=sigma(1<=j<=i)Sj/tj,Ri=sigma(1<=j<=i)1/tj。然后我们将dp[i][j]中的F[k,j]用这些表达式替换能得到:dp[i][j]=min(dp[i][k]-Qk+Sk*Rk-Sk*Rj+Qj),这里很明显的斜率优化了。维护一个下凸包就能在O(1)的时间中转移状态啦。O(n*k)。

 

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define L(i) i<<1
#define R(i) i<<1|1
#define INF  0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-9
#define maxn 1000010
#define MOD 1000000007
const int N=1000010;

long long hea,top,t[N];
long double dx[N],dy[N],dp[2][N],sum[N],sump[N],sumq[N];
int cala(long double x,long double y,long double x1,long double y1,long double x2,long double y2)
{
    return (y2-y1)*(x1-x) >= (y1-y)*(x2-x1);
}
void add(long double x,long double y)
{
    if(top-hea<1)
    {
        top++;
        dx[top] = x;
        dy[top] = y;
        return;
    }
    while(top-hea > 0 && cala(x,y,dx[top],dy[top],dx[top-1],dy[top-1]))
        top--;
    top++;
    dx[top] = x;
    dy[top] = y;
}
int main()
{
    int n,k,now,pre;
    scanf("%d%d",&n,&k);
    for(int i = 1; i <= n; i++)
        scanf("%I64d",&t[i]);
    t[0] = 0ll;
    sum[0] = sump[0] = sumq[0] = 0.0;
    for(int i = 1; i <= n; i++)
    {
        sum[i] = sum[i-1] + 1.0 * t[i];
        sumq[i] = sumq[i-1] + 1.0 / t[i];
        sump[i] = sump[i-1] + sum[i] * 1.0 / t[i];
    }
    now = pre = 0;
    for(int i = 0; i <= n; i++)
        dp[now][i] = sump[i];
    for(int i = 2; i <= k; i++)
    {
        pre = now;
        now ^= 1;
        hea = 1;
        top = 0;
        for(int j = i; j <= n; j++)
        {
            add(sum[j-1],sum[j-1]*sumq[j-1]-sump[j-1]+dp[pre][j-1]);
            while(top-hea > 0 && sumq[j]*(dx[hea]-dx[hea+1]) <= dy[hea]-dy[hea+1])
                hea++;
            dp[now][j] = sump[j] - dx[hea] * sumq[j] + dy[hea];
        }
    }
    printf("%.15f\n", (double)dp[now][n]);
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值