一本通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;}
posted @ 2023-07-25 14:08  Zimo_666  阅读(13)  评论(0编辑  收藏  举报