一本通OJ-粉刷木板
粉刷木板
题意
有\(N\)块木板从左到右排成一行,有\(M\)个工匠对这些木板进行粉刷,每块木板至多被粉刷一次。第\(i\)个木匠要么不粉刷,要么粉刷包含木板\(S_{i}\)且长度不超过\(L_{i}\)的连续的一段木板,每粉刷一块可以得到\(P_{i}\)的报酬。求最大总报酬。
分析
先考虑一个朴素的\(dp\)。我们设\(f[i][j]\)表示我们考虑前\(i\)个工匠,前\(j\)块木板所能得到的最大价值。我们有转移\(f[i][j]=Max(f[i-1][j],f[i][j-1],f[i][k]+(j-k)*p[i])\to k∈[j-l_{i},s[i]-1]\)。
接着我们可以把\(dp\)式转化为\(f[i][j]=Max(f[i][k]-k*p[i]+j*p[i])\)。
我们可以考虑单调队列优化\(dp\)。首先将所有工匠按照\(s[i]\)大小从小到大排序,这样可以保证\(k\)序列不总为\(k\)从而取不到合适的\(k\)。而后我们考虑可以维护一个较优的\(k\)序列,每次操作前先检查队首是否最优。而后我们在\(j>s[i]\&\&s[i]+l[j]\ge j\)时候对\(f[i]\)进行转移。而后我们对队尾的\(k\)进行最优性检查。而后我们将\(j\)入队。可以考虑使用\(deque\)或\(list\)维护。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
int s[N],l[N];
int f[105][N];
int n,m;
int p[N];
int get_ans(int x,int i){
return f[i-1][x]-p[i]*x;
}
struct Str {
int l, p, s;
} str[N];
void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&str[i].l,&str[i].p,&str[i].s);
}
sort(str + 1, str + m + 1, [] (const Str& A, const Str& B) {
return A.s < B.s;
});
for (int i = 1; i <= m; ++i) {
l[i] = str[i].l, p[i] = str[i].p, s[i] = str[i].s;
}
for(int i=1;i<=m;i++){
list<int> q;
if(str[i].s-str[i].l<=0) q.push_front(0);
for(int j=1;j<=n;j++){
f[i][j]=max(f[i-1][j],f[i][j-1]);
while(!q.empty()&&(q.front()>str[i].s-1||q.front()<j-str[i].l)) q.pop_front();
if(!q.empty()&&j>=str[i].s&&str[i].s+str[i].l>=j)f[i][j]=max(get_ans(q.front(),i)+str[i].p*j,f[i][j]);
if (j<str[i].s&&j>=str[i].s-str[i].l) {
while (!q.empty()&&get_ans(q.back(), i)<get_ans(j, i)) q.pop_back();
q.push_back(j);
}
}
}
printf("%d",f[m][n]);
}
int main(){solve();return 0;}