POJ 1821 Fence

http://poj.org/problem?id=1821

题目

有n块木板排成一行,有k个工人,第$i$个工人坐在第$s_i$块木板处,每刷一块木板的工钱是$p_i$,他只能选择粉刷长度不超过$l_i$,包含$s_i$的连续的一段木板,也可以不粉刷。

每块木板要么只由一个工人粉刷,要么不粉刷

问这些工人能得到的工钱的和最大是多少

$n\leqslant16000\quad k\leqslant100$

题解

设dp[i][j]为前i个工人刷了前j块木板

那么

$dp[i][j]=dp[i][j-1]$

$dp[i][j]=dp[i-1][j]$

$dp[i][j]=\max\{dp[i-1][k]+(j-k)\times p_i|k<s_i,j\geqslant s_i,j-k\leqslant l_i\}$

直接转移,时间复杂度$\mathcal{O}(n^2k)$无法接受

改写一下

$dp[i][j]=j\times p_i+\max\{dp[i-1][k]-k\times p_i|j-l_i\leqslant k< si\}$

发现max的范围是不断向后移动的,因此可以使用单调队列(滑动窗口)

那么时间复杂度$\mathcal{O}(nk)$

AC代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#define REP(i,a,b) for(register int i=(a); i<(b); i++)
#define REPE(i,a,b) for(register int i=(a); i<=(b); i++)
#define PERE(i,a,b) for(register int i=(a); i>=(b); i--)
using namespace std;
typedef long long ll;
int n,m;
#define MAXN 16007
#define MAXM 107
struct node {
	int l,p,s;
	bool operator<(const node&r) const {
		return s<r.s;
	}
} a[MAXM];
int dp[MAXM][MAXN];
int dw[MAXN], di[MAXN];
int main() {
	scanf("%d%d", &n, &m);
	REP(i,1,m) scanf("%d%d%d", &a[i].l, &a[i].p, &a[i].s);
	sort(a+1,a+1+m);
	//dp[i][j] = k<si, j>=si, j-k<=li max(dp[i-1][k]+p*(j-k)) (]
	//dp[i][j] = j-li<=k<=si max(dp[i-1][k]-p*k)+p*j;
	//dp[i][j] = dp[i-1][j]
	//dp[i][j] = dp[i][j-1]
	memset(dp[0],0,sizeof(dp[0]));
	REPE(i,1,m) {
		int l=0,r=0;
		REP(k,max(a[i].s-a[i].l,0), a[i].s) {
			int t=dp[i-1][k]-a[i].p*k;
			while(r>l && dw[r-1]<t) r--;
			dw[r]=t;
			di[r]=k;
			r++;
		}
//]		int mj=min(a[i].l+a[i].s, n);____
		dp[i][0]=0;
		REPE(j,1,n) {
			dp[i][j]=max(dp[i][j-1], dp[i-1][j]);
			if(j>=a[i].s) {
				int t=j-a[i].l; while(l<r && di[l]<t) l++;
				if(l<r) dp[i][j]=max(dp[i][j], dw[l]+a[i].p*j);
			}
		}
	}
	printf("%d\n", dp[m][n]);
}

 

上一篇:第六章 课后习题


下一篇:线性表之五,C++代码(学堂在线,华南理工大学)