题目链接
题意
n n n 个木板, m m m 个粉刷匠,每块木板最多刷一次,第 i i i 个粉刷匠要么不刷,要么刷包含 木板 S i S_i Si 的、长度不超过 L i L_i Li 的连续一段木板,每刷一块得到 P i P_i Pi 的钱,求最大能一共能获得多少钱。
思路
单调队列优化DP的模板题?
首先不考虑优化
对粉刷匠的
s
i
s_i
si 进行从小到大排序
可以设
D
P
[
i
]
[
j
]
DP[i][j]
DP[i][j] 表示前
i
i
i 个粉刷匠刷到第
j
j
j 个 时最大值
转移方程如下
1.
D
P
[
i
]
[
j
]
=
m
a
x
(
D
P
[
i
−
1
]
[
j
−
1
]
,
D
P
[
i
]
[
j
−
1
]
)
,
粉
刷
匠
不
刷
,
粉
刷
匠
此
点
不
刷
(
继
承
前
面
的
答
案
)
1.DP[i][j] = max(DP[i-1][j-1],DP[i][j-1]),粉刷匠不刷,粉刷匠此点不刷(继承前面的答案)
1.DP[i][j]=max(DP[i−1][j−1],DP[i][j−1]),粉刷匠不刷,粉刷匠此点不刷(继承前面的答案)
2.
D
P
[
i
]
[
j
]
=
∑
k
=
j
−
l
i
s
i
−
1
m
a
x
(
D
P
[
i
]
[
j
]
,
D
P
[
i
−
1
]
[
k
]
+
p
i
∗
(
j
−
k
)
)
,
枚
举
刷
的
起
点
从
k
+
1
开
始
刷
2.DP[i][j] = \sum_{k=j-{l_i}}^{{s_i}-1}max(DP[i][j],DP[i-1][k]+{p_i}*(j-k)),枚举刷的起点从k+1开始刷
2.DP[i][j]=k=j−li∑si−1max(DP[i][j],DP[i−1][k]+pi∗(j−k)),枚举刷的起点从k+1开始刷
显然三层循环会超时需要优化。
写了几题dp优化都是优化决策点,缩小决策点范围从而使复杂度的乘法变加法,此题亦然。
改变2式提取 j j j, D P [ i ] [ j ] = m a x ( D P [ i ] [ j ] , p i ∗ j + m a x ( ∑ k = j − l i s i − 1 D P [ i − 1 ] [ k ] + p i ∗ k ) ) DP[i][j] = max(DP[i][j],{p_i}*j+ max(\sum_{k=j-{l_i}}^{{s_i}-1}DP[i-1][k]+{p_i}*k)) DP[i][j]=max(DP[i][j],pi∗j+max(∑k=j−lisi−1DP[i−1][k]+pi∗k))
当i固定时,j的改变不会影响决策点k的优劣性。
假设 k 1 < k 2 < = s i − 1 k1<k2<=s_i-1 k1<k2<=si−1,随着 j j j 增加 i i i 不变,k1会更早被移出决策集。
如果还满足 D P [ i − 1 ] [ k 1 ] + p i ∗ k 1 < D P [ i − 1 ] [ k 2 ] + p i ∗ k 2 DP[i-1][k1]+p_i*k1 < DP[i-1][k2]+p_i*k2 DP[i−1][k1]+pi∗k1<DP[i−1][k2]+pi∗k2,显然无论如何 k 2 k2 k2 比 k 1 k1 k1 更优。
所以维护一个单调递减的 D P [ i − 1 ] [ k ] + p i ∗ k DP[i-1][k]+p_i*k DP[i−1][k]+pi∗k 即可。
代码
#include <stdio.h>
#include <algorithm>
using namespace std;
int read(int &_a){return scanf("%d",&_a);}
int rd(){int _tmp; scanf("%d",&_tmp); return _tmp;}
struct Node
{
int l, p, s;
bool operator <(const Node &_tmp) const
{return s < _tmp.s;}
}e[105];
int dp[105][16005], q[16005];
int getd(int i, int j)
{
return dp[i-1][j]-e[i].p*j;
}
int main()
{
int n, m;
while(~scanf("%d%d",&n,&m))
{
for(int i = 1; i <= m; ++i) read(e[i].l), read(e[i].p), read(e[i].s);
sort(e+1,e+m+1);
for(int i = 1; i <= m; ++i)
{
int l = 1, r = 0;
for(int j = max(0,e[i].s-e[i].l); j < e[i].s; ++j)
{
while(l <= r && getd(i,j) >= getd(i,q[r])) --r;
q[++r] = j;
}
for(int j = 1; j <= n; ++j)
{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
if(j >= e[i].s)
{
while(l <= r && q[l] < j-e[i].l) ++l;
if(l <= r) dp[i][j] = max(dp[i][j], dp[i-1][q[l]]+e[i].p*(j-q[l]));
}
}
}
printf("%d\n",dp[m][n]);
}
return 0;
}