zoukankan      html  css  js  c++  java
  • [bzoj2726][SDOI2012]任务安排 ——斜率优化,动态规划,二分,代价提前计算

    题解

    本题的状态很容易设计:
    f[i] 为到第i个物件的最小代价。
    但是方程不容易设计,因为有“后效性”
    有两种方法解决:
    1)倒过来设计动态规划,典型的,可以设计这样的方程:
    dp(i) = min( dp(j) + F(i) * (T(i) - T(j) + S) ) (i < j <= N) F, T均为后缀和. http://www.cnblogs.com/JSZX11556/p/5184251.html
    2)提前计算代价,典型的,可以设计这样的方程:
    「设f[i]为将前i个任务划分完成的最小费用,Ti Fi分别表示t和f的前缀和,则不难写出转移方程式:

        f[i]=min{ f[j]+(F[n]-F[j])*(T[i]-T[j]+s) },1<=j<=i-1」 ——来自神犇hahalidaxin学长
    

    这里我采用了第二种方法,设计这样的方程:
    f[i] = min{f[j] + S * (F[n]-F[j]) + T[i]*(F[i]-F[j]) }
    其中,F,T均是前缀和。
    这是一个(1D/1D)的方程,我们如果直接求解,O(n2)的复杂度,不能满足要求。

    对于一个(1D/1D)的方程,一般采用斜率优化或者四边形不等式进行优化转移。我们考虑斜率优化。
    令k < j < i ,如果j优于k,我们有:
    f[k] - S * F[k] - T[i]F[k] > f[i] - S * F[j] - TiF[j]
    进一步整理,
    f[j]-f[k]+SF[k]-SF[j] < T[i]*(F[j] - F[k]),
    考虑到F是前缀和,并且F没有负值,所以F[x]函数严格单调递增,F[j]-F[k] > 0, 我们两边同时除以(F[j]-F[k]),

    (f[j]-f[k])
    —————— - S < T[i]
    (F[j] - F[k])

    假设T[x]是单调的,我们可以直接通过单调队列转移,复杂度O(1),总复杂度O(n)。
    但是这个题防AC的一个点是:T可以是负数,所以T[i]并不单调。
    所以我们可以使用二分查找,付出O(logn)的时间复杂度,对于本题而言可以接受。 // 另外,听说本题还有使用“CDQ分治”解答的方法
    所以我们就使用O(nlogn)的时间解决了这个问题。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 1e6+5;
    #define ll long long
    ll T[maxn], F[maxn], f[maxn];
    int N, S;
    int head, tail, q[maxn];
    void dp() {
        head = tail = 0;
        for(int i = 1; i <= N; i++) {
            int l = 0, r = tail;
            while(l < r) {
                ll mid = (l+r)/2;
                if((ll)(f[q[mid+1]]-f[q[mid]]) >= (ll)(T[i]+S) * (F[q[mid+1]]-F[q[mid]])) r = mid;
                else l = mid+1;
            }
            int j = q[l];
            f[i] = f[j] + S*(F[N]-F[j])+T[i]*(F[i]-F[j]);
            while(head < tail && (ll)(f[q[tail]]-f[q[tail-1]])*(F[i]-F[q[tail]]) >= (ll)(f[i]-f[q[tail]])*(F[q[tail]]-F[q[tail-1]])) 
                tail--;
            q[++tail] = i;
        }
    }
    int main() {
        //freopen("input", "r", stdin);
        scanf("%d %d", &N, &S);
        for(int i = 1; i <= N; i++) {
          scanf("%lld %lld", &T[i], &F[i]);
          T[i] += T[i-1];
          F[i] += F[i-1];
        }
        dp();
        printf("%lld", f[N]);
        return 0;
    }  
    
  • 相关阅读:
    手起刀落-一起来写经典的贪吃蛇游戏
    同步、异步、回调执行顺序之经典闭包setTimeout分析
    起步
    设计模式之单例模式与场景实践
    青春是如此美好,又怎忍平凡度过
    nvm管理不同版本的node和npm
    起步
    基础
    调用wx.request接口时需要注意的几个问题
    微信小程序实现各种特效实例
  • 原文地址:https://www.cnblogs.com/gengchen/p/6347903.html
Copyright © 2011-2022 走看看