本来挺简单的一道题...
我发现自己不太擅长简化、抽象模型,老是不知道怎么把学过的技巧用上去_(:зゝ∠)_
Kalila and Dimna are two jackals living in a huge jungle. One day they decided to join a logging factory in order to make money.
The manager of logging factory wants them to go to the jungle and cut n trees with heights a1, a2, ..., an. They bought a chain saw from a shop. Each time they use the chain saw on the tree number i, they can decrease the height of this tree by one unit. Each time that Kalila and Dimna use the chain saw, they need to recharge it. Cost of charging depends on the id of the trees which have been cut completely (a tree is cut completely if its height equal to 0). If the maximum id of a tree which has been cut completely is i (the tree that have height ai in the beginning), then the cost of charging the chain saw would be bi. If no tree is cut completely, Kalila and Dimna cannot charge the chain saw. The chainsaw is charged in the beginning. We know that for each i < j, ai < aj and bi > bj and also bn = 0 and a1 = 1. Kalila and Dimna want to cut all the trees completely, with minimum cost.
They want you to help them! Will you?
Input
The first line of input contains an integer n (1 ≤ n ≤ 105). The second line of input contains n integers a1, a2, ..., an (1 ≤ ai ≤ 109). The third line of input contains n integers b1, b2, ..., bn (0 ≤ bi ≤ 109).
It's guaranteed that a1 = 1, bn = 0, a1 < a2 < ... < an and b1 > b2 > ... > bn.
Output
The only line of output must contain the minimum cost of cutting all the trees completely.
Please, do not write the %lld specifier to read or write 64-bit integers in С++. It is preferred to use the cin, cout streams or the %I64d specifier.
Examples
Input
5
1 2 3 4 5
5 4 3 2 0
Output
25
Input
6
1 2 3 10 20 30
6 5 4 3 2 0
Output
138
伐木工人用电锯伐木,一共需要砍n棵树,每棵树的高度为a[i],每次砍伐只能砍1单位高度,之后需要对电锯进行充电,费用为当前砍掉的树中最大id的b[id]值
a[1] = 1 , b[n] = 0,a[i]b[i+1]
问砍完所有的树的最小费用
参考博客&特别鸣谢:
https://www.cnblogs.com/yejinru/p/3348326.html
https://blog.csdn.net/lerenceray/article/details/9365947
首先,由于b[n] = 0 , 所以很容易弄出一个O(n^2)的状态转移方程:
dp[1]=0;
for(int i=2;i<=n;i++)
{
dp[i]=INF;
for(int j=1;j
这种朴素的转移方程显然会TLE
注意到以上的方程,其实就是1D1D模型(具体百度),可以利用斜率进行优化
题意大致是砍树,不一定按顺序砍,目的是为了使充电费用最低,看完标号最大的树,充电就不再需要费用,所以关键在于求砍完标号最大的树所需要的最小花费
DP优化,记dp[ i ]为砍倒第i棵树所需要的最小花费,
所以所以对于dp[ n ], dp[ n ] = min{ dp[ j ] + b[ j ] * a[ n ] }, j = 1, 2, 3, ...... n - 1
注意这里题目条件单调的,这可以作为斜率DP的一个标志
分析DP的具体操作:
设现在要求dp[ k ],在k之前的任意两个数,我们设为i, j, 且i < j < k
那么dp[ j ] + b[ j ] * a[ k ] 和 dp[ i ] + b[ i ] * a[ k ]两个方案那个更优呢?
作差比较, 移项化简得:如果 (dp[ j ] - dp[ i ]) / (b[ i ] - b[ j ]) < a[ k ] 则 方案 j 优于方案 i(这里要注意b[ ]递减,移项变号),
我们把左边看作一个两点的斜率,得到一个函数G(j, i)如果G(j, i)< a[ k ] 则 j 优于 i,可以删除 i 方案,且以后 j 之后再有其他数,i 也不可能成为最优方案,因为 j 比 i 优
另外 当G(j, i) < G ( k , j )时, 可以删除 j 方案,因为这样 j 永远不会是最优方案, 以上两步优化维护了一个单调队列, 图形上看相邻两点的斜率单调递增或递减,所以叫斜率DP优化吧?
原来复杂度n*n的降为了O(n),因为每个方案之进入队列一次,被提出以后不再加入
简单来说:
斜率优化无非是:假设j
dp[ k ]+b[ k ] * a[ i ] < dp[ j ]+b[ j ]*a[ i ]
由于b[ k ]
因此移项之后为:
( dp[ k ] - dp[ j ] ) / ( b[ j ] - b[ k ] ) < a[ i ]
因此,我们可以根据斜率进行优化,具体可以看代码,这部分比较好懂
Ps.可是为什么head=1,tail=2才可以呢...又是区间开闭的问题吗...?
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN=1e5;
ll a[MAXN+5],b[MAXN+5],que[MAXN+5],dp[MAXN+5];
ll n;
ll f1(ll x,ll y)
{
return dp[que[x]]-dp[que[y]];
}
ll f2(ll x,ll y)
{
return b[que[x]]-b[que[y]];
}
void Solve()
{
ll head=1,tail=2;
que[1]=1,dp[1]=0;
for(int i=2;i<=n;i++)
{
while(head2&&(double)f1(tail-1,tail-2)/f2(tail-2,tail-1)>=(double)(dp[i]-dp[que[tail-1]])/(b[que[tail-1]]-b[i]))
tail--;
que[tail++]=i;
}
printf("%lld\n",dp[n]);
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&b[i]);
Solve();
return 0;
}