BZOJ 2726 [SDOI2012] 任务安排

更好的阅读体验 【Press Here】

Problem

传送门 >ω<

题目大意:
按顺序给定 n n 个子任务,每个任务用时 ti ,费用系数 fi f i
连续的多个(一个)子任务合成为大任务,大任务的用时和费用系数为所有子任务之和,启动一个大任务需要时间 S S ,每次进行一个大任务,其费用为 结束时间 * 费用系数 ,问最小费用为多少(大任务也要按照先后顺序进行)

要求所有子任务都被包括在大任务之中

题面复杂得一匹…

Solution

因为根据习惯我们用 f 来进行动态规划,所以将 g g f 互换一下

先从比较简单的开始
sumg[i] s u m g [ i ] g[i] g [ i ] 前缀和
sumt[i] s u m t [ i ] r[i] r [ i ] 前缀和
f[i] f [ i ] 为 将前 i i 个小任务分成若干个大任务所需要的最小费用
我们先不考虑每次机器启动的 S ,易得转移

f[i]=min(f[j],sumt[i](sumg[i]sumg[j])) f [ i ] = m i n ( f [ j ] , s u m t [ i ] ∗ ( s u m g [ i ] − s u m g [ j ] ) )

经过观察发现,每次加入一个 S S 都会对后面每个点增加一个贡献 sg[k](k>j) ,即 ssumg[n]sumg[j] s ∗ s u m g [ n ] − s u m g [ j ]

所以转移方程化为

f[i]=min(f[j]+sumt[i](sumg[i]sumg[j])+s(sumg[n]sumg[j])) f [ i ] = m i n ( f [ j ] + s u m t [ i ] ∗ ( s u m g [ i ] − s u m g [ j ] ) + s ∗ ( s u m g [ n ] − s u m g [ j ] ) )

显然这样就能使用 O(n2) O ( n 2 ) 的算法 通过 60% 60 % 的数据

那么如何进一步降低复杂度呢?

假设现在 i i 状态从 k 状态转移比 j j 状态要优,则有

f[j]+sumt[i](sumg[i]sumg[j])+s(sumg[n]sumg[j]))>f[k]+sumt[i](sumg[i]sumg[k])+s(sumg[n]sumg[k]))

化简可得

f[j]ssumg[j]sumt[i]sumg[j]>f[k]ssumg[k]sumt[i]sumg[k] f [ j ] − s ∗ s u m g [ j ] − s u m t [ i ] ∗ s u m g [ j ] > f [ k ] − s ∗ s u m g [ k ] − s u m t [ i ] ∗ s u m g [ k ]

f[k]f[j]sumg[k]sumg[j]<s+sumt[i] f [ k ] − f [ j ] s u m g [ k ] − s u m g [ j ] < s + s u m t [ i ]

其实这就已经可以做了,显然需要维护一个下凸包,在插入的时候弹出无用状态

但是后面的斜率 s+sumt[i] s + s u m t [ i ] 没有单调性,所以说就不能够从状态头部弹出节点,不能保证头部状态最优,所以在可能的状态序列中二分查找,直到右侧节点不优于当前节点为止

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000010;
#define mid ((l + r) >> 1)
ll t[N] , g[N];
ll f[N];
ll sta[N] , top;
ll n , s;
ll read() {
    ll ans = 0 , flag = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') flag = - 1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {ans = ans * 10 + ch - '0'; ch = getchar();}
    return ans * flag;
}
bool check(int x , int i) {
    if(x == top) return 0;
    if(f[sta[x + 1]] - f[sta[x]] <= (g[sta[x + 1]] - g[sta[x]]) * (s + t[i])) return 1;
    return 0;
}
bool slope(int x , int y , int z) {
    return (f[x] - f[y]) * (g[y] - g[z]) <= (f[y] - f[z]) * (g[x] - g[y]);
}
int work(int i) {
    int l = 1 , r = top;
    if(l == r) return sta[l];
    while(l < r) {
        if(check(mid , i)) l = mid + 1;
        else r = mid;
    }
    return sta[l];
}
int main() {
    n = read(); s = read();
    for(int i = 1 ; i <= n ; ++ i) {
        t[i] = read(); t[i] += t[i - 1];
        g[i] = read(); g[i] += g[i - 1];
    }
    sta[++ top] = 0;
    for(int i = 1 ; i <= n ; ++ i) {
        int j = work(i);
        f[i] = f[j] + t[i] * (g[i] - g[j]) + s * (g[n] - g[j]);
        while(top >= 2) {
            if(slope(i , sta[top] , sta[top - 1])) sta[top --] = 0;
            else break;
        }
        sta[++ top] = i;
    }
    printf("%lld\n" , f[n]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值