更好的阅读体验 【Press Here】
Problem
题目大意:
按顺序给定
n
n
个子任务,每个任务用时 ,费用系数
fi
f
i
连续的多个(一个)子任务合成为大任务,大任务的用时和费用系数为所有子任务之和,启动一个大任务需要时间
S
S
,每次进行一个大任务,其费用为 结束时间 * 费用系数 ,问最小费用为多少(大任务也要按照先后顺序进行)
要求所有子任务都被包括在大任务之中
题面复杂得一匹…
Solution
因为根据习惯我们用 来进行动态规划,所以将 g g 互换一下
先从比较简单的开始
设
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 S 都会对后面每个点增加一个贡献 ,即 s∗sumg[n]−sumg[j] s ∗ s u m g [ n ] − s u m g [ j ]
所以转移方程化为
显然这样就能使用 O(n2) O ( n 2 ) 的算法 通过 60% 60 % 的数据
那么如何进一步降低复杂度呢?
假设现在 i i 状态从 状态转移比 j j 状态要优,则有
化简可得
其实这就已经可以做了,显然需要维护一个下凸包,在插入的时候弹出无用状态
但是后面的斜率 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;
}