analysis
先把工匠按照s排序,然后
DP方程:
设f[i][j]为前i个工匠刷前j块木板的最大收益:
f [ i ] [ j ] = m a x { f [ i − 1 ] [ j ] f [ i ] [ j − 1 ] f [ i − 1 ] [ k ] + p [ i ] × ( j − k ) , k ∈ [ s [ i ] − l [ i ] , s [ i ] − 1 ] , j ∈ [ s [ i ] , n ] , j − k < = L f[i][j]=max\begin{cases} f[i-1][j]\\f[i][j-1]\\f[i-1][k]+p[i]\times (j-k),k\in[s[i]-l[i],s[i]-1],j\in[s[i],n],j-k<=L \end{cases} f[i][j]=max⎩⎪⎨⎪⎧f[i−1][j]f[i][j−1]f[i−1][k]+p[i]×(j−k),k∈[s[i]−l[i],s[i]−1],j∈[s[i],n],j−k<=L
这个时候发现第三个转移会把时间复杂度提高到 O ( n 2 m ) O(n^2m) O(n2m),于是考虑如何优化
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ k ] + p [ i ] × ( j − k ) ) , k ∈ [ s [ i ] − l [ i ] , s [ i ] − 1 ] , j ∈ [ s [ i ] , n ] , j − k < = L f[i][j]=max(f[i-1][k]+p[i]\times (j-k)),k\in[s[i]-l[i],s[i]-1],j\in[s[i],n],j-k<=L f[i][j]=max(f[i−1][k]+p[i]×(j−k)),k∈[s[i]−l[i],s[i]−1],j∈[s[i],n],j−k<=L
式子可化为
f [ i ] [ j ] = p [ i ] × j + m a x ( f [ i − 1 ] [ k ] − p [ i ] × k ) f[i][j]=p[i]\times j+max(f[i-1][k]-p[i]\times k) f[i][j]=p[i]×j+max(f[i−1][k]−p[i]×k)
方便起见,设 F ( i , k ) = f [ i − 1 ] [ k ] − p [ i ] × k F(i,k)=f[i-1][k]-p[i]\times k F(i,k)=f[i−1][k]−p[i]×k
发现k的取值范围
k ∈ [ s [ i ] − l [ i ] , s [ i ] − 1 ] 且 j − k < = L → k ∈ [ j − L , s [ i ] − 1 ] 且 j > = s [ i ] k\in[s[i]-l[i],s[i]-1]且j-k<=L\rightarrow k \in [j-L,s[i]-1]且j>=s[i] k∈[s[i]−l[i],s[i]−1]且j−k<=L→k∈[j−L,s[i]−1]且j>=s[i]
由于下界 j − L j-L j−L随着j增大而增大,所以k的下界单调递增,如果i固定, s [ i ] − 1 s[i]-1 s[i]−1就固定,相当于是在一段滑动窗口内求一个最大值,这就可以用到单调队列了
接下来应该分析单调队列里的决策的单调性:如果对于
k
1
<
k
2
<
s
[
i
]
,
F
(
i
,
k
1
)
<
F
(
i
,
k
2
)
k_1<k_2<s[i],F(i,k_1)<F(i,k_2)
k1<k2<s[i],F(i,k1)<F(i,k2),说明
k
2
k_2
k2比
k
1
k_1
k1后出队,也就是
k
1
k_1
k1比
k
2
k_2
k2弱(k2就是那个比k1强还比他小的那个),那么很显然k1不会成为任何一个状态j的最优决策,出队.于是我们应该维护一个随着k递增,
F
(
i
,
k
)
F(i,k)
F(i,k)递减的一个队列
code
#include <iostream>
#include <fstream>
#include <cstdio>
#include <cmath>
#include <map>
#include <set>
#include <bitset>
#include <ctime>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
#include <vector>
using namespace std;
#define loop(i,start,end) for(register int i=start;i<=end;++i)
#define clean(arry,num) memset(arry,num,sizeof(arry))
#define anti_loop(i,start,end) for(register int i=start;i>=end;--i)
#define ll long long
template<typename T>void read(T &x){
x=0;char r=getchar();T neg=1;
while(r>'9'||r<'0'){if(r=='-')neg=-1;r=getchar();}
while(r>='0'&&r<='9'){x=(x<<1)+(x<<3)+r-'0';r=getchar();}
x*=neg;
}
const int maxn=16000+10;
const int maxm=110;
const int maxp=10000+10;
class duilie{
public:
struct node{
int pos;
int w;
node(int pos=0,int w=0):pos(pos),w(w){}
};
node m[maxn];
int l,r;
bool empty(){return l>r;}
void init(){l=1;r=0;}
void push_back(int pos,int w){m[++r]=node(pos,w);}
void pop_front(){++l;}
void pop_back(){--r;}
node front(){return m[l];}
node back(){return m[r];}
}que;
int n,m;
struct worker{
int l,p,s;
worker(int l=0,int p=0,int s=0):l(l),p(p),s(s){}
}x[maxm];
int f[maxm][maxn];
inline bool cmp(worker a,worker b){return a.s<b.s;}
inline int F(int i,int k){return f[i-1][k]-x[i].p*k;}
//#define F(i,k) (f[i-1][k]-x[i].p*k)
int main(){
#ifndef ONLINE_JUDGE
freopen("datain.txt","r",stdin);
#endif
que.init();
read(n);read(m);
loop(i,1,m)read(x[i].l),read(x[i].p),read(x[i].s);
sort(x+1,x+1+m,cmp);
clean(f,0);
loop(i,1,m){
que.init();
loop(k,max(0,x[i].s-x[i].l),x[i].s-1){
//2 注意这里k的范围,如果k小于了x[i].s-x[i].l,那这个是无意义的,显然木匠i刷不到那里去
while(que.empty()==false&&que.back().w<=F(i,k))que.pop_back();
que.push_back(k,F(i,k));
}
loop(j,1,n){//1 这个j的范围不应该是[x[i].s,x[i].s+x[i].l],因为x[i].s+x[i].l后面还有状态需要更新
f[i][j]=max(f[i][j-1],f[i-1][j]);
if(j>=x[i].s){
while(que.empty()==false&&que.front().pos<j-x[i].l)que.pop_front();
if(que.empty()==false)f[i][j]=max(f[i][j],que.front().w+x[i].p*j);
}
}
}
printf("%d\n",f[m][n]);
return 0;
}
时间复杂度分析:
首先两层循环就已经是
O
(
N
M
)
O(NM)
O(NM)的了:
loop(i,1,m){
//更新单调队列
loop(j,1,n){
//更新单调队列
//更新f[i][j]
}
}
然后唯一有点麻烦的是里面更新单调队列的时间复杂度计算
首先注意到k的范围:
在不考虑j状态变量的条件下k的范围是 [ m a x ( 0 , x [ i ] . s − x [ i ] . l ) , x [ i ] . s − 1 ] [max(0,x[i].s-x[i].l),x[i].s-1] [max(0,x[i].s−x[i].l),x[i].s−1],循环外需要把这些可行的状态全部放到队列中去,时间复杂度大约是 O ( n ) O(n) O(n),但由于这个操作是和里面循环并列,所以不影响全局时间复杂度
循环里面更新单调队列时需要考虑j变量的值,此时的k的范围就是 [ j − x [ i ] . l , x [ i ] . s − 1 ] [j-x[i].l,x[i].s-1] [j−x[i].l,x[i].s−1],注意到k的下界 j − x [ i ] . l j-x[i].l j−x[i].l是逐渐变大的(也就是李煜东书上写的上下界单调变化),所以即使每一次更新单调队列的时候不能保证只弹出一个状态,但这n个物品一定会在这个循环次数为n的循环里面被全部弹出来,所以均摊转移复杂度为O(1)
所以最后这个算法的复杂度还是 O ( N M ) O(NM) O(NM)的