POJ 1821 Fence

在这里插入图片描述

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]=maxf[i1][j]f[i][j1]f[i1][k]+p[i]×(jk),k[s[i]l[i],s[i]1],j[s[i],n],jk<=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[i1][k]+p[i]×(jk)),k[s[i]l[i],s[i]1],j[s[i],n],jk<=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[i1][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[i1][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]jk<=Lk[jL,s[i]1]j>=s[i]

由于下界 j − L j-L jL随着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].sx[i].l),x[i].s1],循环外需要把这些可行的状态全部放到队列中去,时间复杂度大约是 O ( n ) O(n) O(n),但由于这个操作是和里面循环并列,所以不影响全局时间复杂度

循环里面更新单调队列时需要考虑j变量的值,此时的k的范围就是 [ j − x [ i ] . l , x [ i ] . s − 1 ] [j-x[i].l,x[i].s-1] [jx[i].l,x[i].s1],注意到k的下界 j − x [ i ] . l j-x[i].l jx[i].l是逐渐变大的(也就是李煜东书上写的上下界单调变化),所以即使每一次更新单调队列的时候不能保证只弹出一个状态,但这n个物品一定会在这个循环次数为n的循环里面被全部弹出来,所以均摊转移复杂度为O(1)

所以最后这个算法的复杂度还是 O ( N M ) O(NM) O(NM)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AndrewMe8211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值