题目描述
N
N
N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这
N
N
N个任务被分成若干批,每批包含相邻的若干任务。从时刻
0
0
0开始,这些任务被分批加工,第
i
i
i个任务单独完成所需的时间是
T
i
T_i
Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数
C
i
C_i
Ci。
请确定一个分组方案,使得总费用最小。
数据规模
1 ≤ N ≤ 3 ∗ 1 0 5 0 ≤ S , C ≤ 512 − 512 ≤ T ≤ 512 1≤N≤3*10^5 0≤S,C≤512 -512≤T≤512 1≤N≤3∗1050≤S,C≤512−512≤T≤512
思路
首先,这还是个很明显的划分类
D
P
DP
DP。
直接上状态:
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前i个任务分成j批的最小代价。
易得
f
[
i
]
[
j
]
=
m
i
n
f
[
k
]
[
j
−
1
]
+
(
j
∗
s
+
s
u
m
T
[
i
]
)
∗
(
s
u
m
C
[
i
]
−
s
u
m
C
[
k
]
)
f[i][j] = min{f[k][j - 1] + (j * s + sum_T[i]) * (sum_C[i] - sum_C[k])}
f[i][j]=minf[k][j−1]+(j∗s+sumT[i])∗(sumC[i]−sumC[k])
但是这个状态已经是
O
(
n
2
)
O(n^2)
O(n2)级别的了
/-----------华---------丽-----------丽---------的-----------分---------割---------线-----------/
然后,我们会发现限制状态的最大因素就是
j
j
j。
整个转移中唯一与j有关的就是
“
j
∗
s
”
“j * s”
“j∗s”
因此,我们是否可以想办法将它去掉呢?
考虑在第i个任务结束后重新分一组,那么对第
i
+
1
i + 1
i+1 ~
n
n
n 个任务的影响就是加上了
(
s
u
m
C
[
n
]
−
s
u
m
C
[
i
]
)
∗
s
(sum_C[n] - sum_C[i]) * s
(sumC[n]−sumC[i])∗s
那么,我们就可以将j这维状态直接去掉
状态就变成了:
f
[
i
]
f[i]
f[i]表示前i个任务的最小代价
转移:
f
[
i
]
=
m
i
n
f
[
i
]
,
f
[
j
]
+
(
s
u
m
C
[
i
]
−
s
u
m
C
[
j
]
)
∗
(
s
+
s
u
m
T
[
i
]
)
+
(
s
u
m
C
[
n
]
−
s
u
m
C
[
i
]
)
∗
s
f[i] = min{f[i] , f[j] + (sum_C[i] - sum_C[j]) * (s + sum_T[i])} + (sum_C[n] - sum_C[i]) * s
f[i]=minf[i],f[j]+(sumC[i]−sumC[j])∗(s+sumT[i])+(sumC[n]−sumC[i])∗s
-----------华---------丽-----------丽---------的-----------分---------割---------线-----------
然后,我们又发现,这是个非常经典的 1 D / 1 D 1D/1D 1D/1D动态规划
显然是无法通过 30 W 30W 30W级别的数据。
考虑优化,对于一道一维状态的 D P DP DP题,优化方向依然只有一个——转移。
(否则把状态优化了不就成了贪心吗?)
如何减少重复或不必要的枚举呢?
设 1 ≤ j 1 < j 2 < i 1 ≤ j1 < j2 < i 1≤j1<j2<i
则对于 i i i的决策, j 2 j_2 j2比 j 1 j_1 j1优等价于满足
f [ j 2 ] + ( s u m C [ i ] − s u m C [ j 2 ] ) ∗ ( s + s u m T [ i ] ) < f[j_2] + (sum_C[i] - sum_C[j_2]) * (s + sum_T[i]) < f[j2]+(sumC[i]−sumC[j2])∗(s+sumT[i])<
f [ j 1 ] + ( s u m C [ i ] − s u m C [ j 1 ] ) ∗ ( s + s u m T [ i ] ) f[j_1] + (sum_C[i] - sum_C[j_1]) * (s + sum_T[i]) f[j1]+(sumC[i]−sumC[j1])∗(s+sumT[i])
简单的打开并整理过后就得到了
( f [ j 2 ] − f [ j 1 ] ) − s ∗ ( s u m C [ j 2 ] − s u m C [ j 1 ] ) (f[j_2]-f[j_1]) - s * (sum_C[j_2] - sum_C[j_1]) (f[j2]−f[j1])−s∗(sumC[j2]−sumC[j1])
------------------------------------------------------------------------ < s u m T [ i ] < sum_T[i] <sumT[i]
s u m C [ j 2 ] − s u m C [ j 1 ] sum_C[j_2] - sum_C[j_1] sumC[j2]−sumC[j1]
但这道题与 h d u 3057 hdu3057 hdu3057不同之处便是 s u m T [ i ] sum_T[i] sumT[i]不单调了
但是,仔细思考,这并没有破坏队尾维护的性质,即我们依然可以维护一个单调队列(实则是一个单调栈)
而区别就是现在取值的时候,要在这个单调序列中二分出一个斜率与 s u m T [ i ] sum_T[i] sumT[i]最为接近的值
( T i p s Tips Tips:写读优的同志们千万别忘了:此题有负数!此题有负数!此题有负数!本人就 W A WA WA了半个小时 Q A Q QAQ QAQ)
注:对斜率优化基础还不理解的可以参考斜率优化基础
代码
#include<cstdio>
using namespace std;
struct node
{
long long t , c;
}
sum[300005];
long long f[300005];
int q[300005];
long long s;
inline int read()
{
char ch = getchar();
int flag = 1;
while(ch < '0' || ch > '9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
int x = 0;
while(ch >= '0' && ch <= '9') x = x * 10 + ch - 48 , ch = getchar();
return x * flag;
}
inline bool cmp1(int j2 , int j1 , long long k)
{
return f[j2] - f[j1] - s * (sum[j2].c - sum[j1].c) <= k * (sum[j2].c - sum[j1].c);
}
inline bool cmp2(int j3 , int j2 , int j1)
{
return (f[j3] - f[j2] - s * (sum[j3].c - sum[j2].c)) * (sum[j2].c - sum[j1].c) <= (f[j2] - f[j1] - s * (sum[j2].c - sum[j1].c)) * (sum[j3].c - sum[j2].c);
}
inline int find(int l , int r , long long key)
{
while(l < r)
{
int mid = (l + r) >> 1;
if(cmp1(q[mid + 1] , q[mid] , key)) l = mid + 1;
else r = mid;
}
return q[r];
}
int main()
{
int n = read();
s = read();
for(int i = 1;i <= n;i++)
{
int t = read() , c = read();
sum[i].t = sum[i - 1].t + t;
sum[i].c = sum[i - 1].c + c;
}
int head = 1 , tail = 1;
q[1] = 0;
f[0] = 0;
for(int i = 1;i <= n;i++)
{
int loc = find(head , tail , sum[i].t);
f[i] = f[loc] + (s + sum[i].t) * (sum[i].c - sum[loc].c) + (sum[n].c - sum[i].c) * s;
while(head < tail && cmp2(i , q[tail] , q[tail - 1])) tail--;
q[++tail] = i;
}
printf("%lld\n",f[n]);
return 0;
}