题目描述
有N块木板从左到右排成一行,有M个工匠对这些木板进行粉刷,每块木板至多被粉刷一次。
第 i 个木匠要么不粉刷,要么粉刷包含木板 Si 的,长度不超过 Li 的连续的一段木板,每粉刷一块可以得到 Pi 的报酬。
不同工匠的Si不同。
请问如何安排能使工匠们获得的总报酬最多。
输入格式
第一行包含两个整数N和M。
接下来M行,每行包含三个整数Li,Pi,Si。
输出格式
输出一个整数,表示结果。
数据范围
1≤N≤16000,
1≤M≤100,
1≤Pi≤10000
输入样例:
8 4
3 2 2
3 2 3
3 3 5
1 1 7
输出样例:
17
解题报告
题意理解
一个区间的木块要粉刷,每一个木块最多只能粉刷一次,可以不粉刷,对于一个粉刷匠i而言,他必须粉刷第Si木板,而且只能粉刷一段连续的木块,并且要求粉刷长度不超过Li,而且每刷一个木板,就可以得到Pi的报酬,现在要求报酬最大化,
解题思路
①首先将所有粉刷匠,按照必须刷的小木块Si从小到大排序.
上面这个操作为了保证我们可以顺序处理.
②我们可以设f[i][j]表示为,前i个粉刷匠,刷了前i个木块.可以有些木块选择不刷
状态确定好了后,我们分两种情况讨论.
-
第i个粉刷匠不工作,那么
f[i][j]=f[i−1][j] -
第j个木板不刷,那么
f[i][j]=f[i][j−1]
.
结合上面的讨论,我们不难发现,
f[i][j]=max(f[i−1][j],f[i][j−1])
接下来的问题就是,如果说粉刷匠工作,而且也还刷第j个木块,那么我们不得不仔细思考.
对于一个粉刷匠而言,如果说聘请他工作,那么显然我们有几个条件.
- 他粉刷的区间长度,至少为1,最多为Li.
- 他粉刷的区间内必须包括Si这个小木块.
- 粉刷区间左端点,必须小于等于Si
综上所述我们不妨设置一个粉刷匠粉刷的区域为[k+1,j]
那么根据上面所说的条件,我们将它转换为数学计算机语言,如下面这个式子所示.
k+1≤si≤j1≤j−(k+1)≤LiK≤Si−1
综上所述,我们可以将状态转移方程一步步出来.
f[i][j]=max
得出了一个朴素的状态转移方程,我们不得不进行转换一下.
f[i][j]=\max_{j-L_i \le k \le S_i-1} (f[i][j],f[i-1][k]+P_i*(j-(k+1)+1)) \\
我们不妨去掉一个括号.
f[i][j]=\max_{j-L_i \le k \le S_i-1} (f[i][j],f[i-1][k]+P_i*(j-k)) \\
上面的一次次变换,让我们发现了,如果说我们要求的f[i][j]要选取到最大值,那么我们核心目标点就是让Max函数内部的
f[i-1][k]-P_i*k
尽量地大.
尽然如此的话,我们发现K的取值是一个范围,但是我们并不关心这个范围内所有的数值,我们唯一的关心点就是这个范围的最大值.也就是最大的f[i-1][k]
一个区间,最大值,这些关键字眼,不得不让我们思考一下单调队列这种优秀的数据结构.因此我们把中心放到单调队列上面.
单调队列的核心要点,就是生存能力的判断.
我们逐步入手,下面给出几个判断依据.
我们设当前有两个点,一个是k_1,另外一个是k_2.
我们发现当前点,如果说k_1<k_2,也就是k_2后出现.
我们将k_1,k_2代入到我们的状态转移方程中的决定部分.
那么将k_1代入
f[i-1][k_1]-P_i * k_1
再将k_2代入其中
f[i-1][k_2]-P_i * k_2
我们发现如果说我们再满足下面这个条件的话,那么k_2一定优于k_1
就好比如说,一个人比你小,还比你强,那么你就真的比不过他了.
代码分析
#include <bits/stdc++.h>
using namespace std;
const int M=16000+100;
const int N=110;
int n,m,i,j,k,f[N][M];
struct node
{
int p,l,s;//单价;最高长度;必备点
} a[M];
deque<int> q;
int cmp(node a,node b)//排序
{
return a.s<b.s;
}
void init()
{
ios::sync_with_stdio(false);
cin>>m>>n;
for(int i=1;i<=n;i++)
cin>>a[i].l>>a[i].p>>a[i].s;
sort(a+1,a+1+n,cmp);
}
int date(int i,int k)
{
return f[i-1][k]-a[i].p*k;
}
void work()
{
for(int i=1;i<=n;i++)//粉刷匠
{
for(int k=max(0,a[i].s-a[i].l);k<a[i].s;k++)
{
while (q.size() && date(i,q.back())<=date(i,k))//不是最优值
q.pop_back();
q.push_back(k);
}
for(int j=1;j<=m;j++)//粉刷墙
{
f[i][j]=max(f[i-1][j],f[i][j-1]);//第一步取最大值
if (j>=a[i].s)
{
while(q.size() && q.front()<j-a[i].l)//不在候选范围内了.
q.pop_front();
if (q.size())
f[i][j]=max(f[i][j],a[i].p*j+date(i,q.front()));//计算
}
}
}
cout<<f[n][m];
}
int main()
{
init();
work();
return 0;
}
您就是那个比我小,还比我强万倍的人之一啊您虚伪啊
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
您就是那个比我小,还比我强万倍的人之一啊
fAKe
虽然变量名没有关系,但
int date
这个函数名的意思是日期啊,按意思应该叫做数据data
才对吧我们可以设f[i][j]表示为,前i个粉刷匠,刷了前i个木块
秦佬是第j个木块吧
太强啦QaQ
大佬 讲解写错了吧 f[i][j] = max (f[i][j], f[i-1][k] - k * a[i].p + j * a[i].p);
大佬,每次循环粉刷匠的时候是不是要清空下队列,之前推进去的会不会对后来的造成影响
不需要吧.
枚举的 j 只有在 >=s[i] 时才会从队列中取决策点,然后在有决策点的情况下 j 的范围不超过 s[i]+l[i]-1 ,也就是说每次枚举的 i 都可完全转移,当枚举下一个 i+1 时,此时队列中不对存在决策点,所以清空或者不清空是一样的。
简单来说:又老又丑
hh