这是一道单调队列的板题
题目链接(Luogu)
Codeforces
题意
伐木工人用电锯伐木,一共需要砍
n
n
n棵树,每棵树的高度为
a
i
a_i
ai,每次砍伐只能砍
1
1
1单位高度,之后需要对电锯进行充电,费用为当前砍掉的树中最大
i
d
id
id的
b
i
d
b_{id}
bid值
题
目
保
证
a
[
1
]
=
1
,
b
[
n
]
=
0
,
a
[
i
]
<
a
[
i
+
1
]
,
b
[
i
]
>
b
[
i
+
1
]
(
i
<
j
)
题目保证a[1] = 1 , b[n] = 0,a[i]<a[i+1],b[i]>b[i+1]\ (i < j)
题目保证a[1]=1,b[n]=0,a[i]<a[i+1],b[i]>b[i+1] (i<j)
思路
因为有
b
[
n
]
=
0
b[n] = 0
b[n]=0,所以当砍掉第
n
n
n棵树时就不需要花钱了
从这个角度入手,可以考虑定义
d
p
[
i
]
dp[i]
dp[i]为砍第
i
i
i棵树的花费
那么就可以轻松的得到一个
O
(
n
2
)
O(n^2)
O(n2)的状态转移方程式:
d
p
[
i
]
=
m
i
n
(
d
p
[
i
]
,
d
p
[
j
]
+
a
[
i
]
∗
b
[
j
]
)
,
j
<
i
dp[i] = min(dp[i], dp[j] + a[i]*b[j]),\ j < i
dp[i]=min(dp[i],dp[j]+a[i]∗b[j]), j<i
但是一看数据范围
n
≤
1
e
5
n \le 1e5
n≤1e5,朴素的
d
p
dp
dp明显行不通
我们再来看这个转移式,因为每次转移只跟
i
,
j
i,j
i,j有关,我们考虑使用斜率优化
假设有:
j
<
k
j < k
j<k且
j
j
j比
k
k
k优
那么有:
d
p
[
j
]
+
a
[
i
]
∗
b
[
j
]
<
d
p
[
k
]
+
a
[
i
]
∗
b
[
k
]
dp[j] +a[i]*b[j]<dp[k]+a[i]*b[k]
dp[j]+a[i]∗b[j]<dp[k]+a[i]∗b[k]
移项过后就是:
d
p
[
k
]
−
d
p
[
j
]
b
[
j
]
−
b
[
k
]
>
a
[
i
]
\frac{dp[k]-dp[j]}{b[j]-b[k]}>a[i]
b[j]−b[k]dp[k]−dp[j]>a[i]
这个就是我们的斜率式了
维护一个队列就可以使复杂度降低到
O
(
n
)
O(n)
O(n)
代码
具体实现请看代码
const int N = 1e5 + 5;
ll dp[N];
int n, a[N], b[N], q[N], head, tail;
double getk(int x, int y)
{
return 1.0 * (dp[y] - dp[x]) / (b[x] - b[y]);
}
int main()
{
ios :: sync_with_stdio(0);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
q[++tail] = 1;
head = 1;
for (int i = 2; i <= n; i++)
{
while (head < tail && getk(q[head], q[head + 1]) < a[i])
head++;
dp[i] = dp[q[head]] + 1ll * a[i] * b[q[head]];
while (head < tail && getk(q[tail], i) <= getk(q[tail - 1], q[tail]))
tail--;
q[++tail] = i;
}
cout << dp[n];
return 0;
}