1 题意
有一些木材需要加工,每个木材都用长度和重量,用
(
l
,
w
)
(l,w)
(l,w)标识,加工机器在加工每个木材之前都需要一分钟的时间重启,除非前一个木材的长度和重量都不比当前木材大。问:加工这些木材,加工机器的重启时间最少是多少?
链接:link
2 思路
原题等价于将原木材划分成若干个非递减子序列,要求划分数最少。
上述问题进而可以转化为,先将木材按长度(重量)排序,再按重量(长度)将木材划分成多个非递减序列,要求划分数最少。
上述问题的解答,可参考:link。
最终可知,原问题等价于,先将木材按长度(重量)排序,再求重量(长度)的最长递减序列的长度。
2.1 单调栈
由于要求最长递增子序列的长度,这里先将原序列反转,转而求最长递增子序列。
关于如何用单调栈求最长递增子序列,可参考:link。
2.1.1 时间复杂度分析
最坏情况下每次入栈的元素都需要用二分查找替换,时间复杂度为 O ( n l o g ( n ) ) \mathcal{O}(nlog(n)) O(nlog(n))
2.1.2 实现
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=5e3+10;
struct Node{
int l,w;
}a[N];
bool operator<(const Node& a,const Node& b){
return a.l<b.l||a.l==b.l&&a.w<b.w;
}
int main(){
int T;scanf("%d",&T);
while(T--){
int n;scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d %d",&a[i].l,&a[i].w);
sort(a,a+n);
reverse(a,a+n);
vector<int> stk;
for(int i=0;i<n;i++){
if(stk.empty()||stk.back()<a[i].w) stk.push_back(a[i].w);
else *lower_bound(stk.begin(),stk.end(),a[i].w)=a[i].w;
}
printf("%d\n",stk.size());
}
return 0;
}
2.2 动态规划
d
p
i
dp_{i}
dpi表示以第
i
i
i个数结尾的最长递减子序列的长度。
d
p
i
=
max
j
⩽
i
−
1
∧
a
j
>
a
i
(
a
j
)
+
1
dp_{i}= \max_{j \leqslant i-1 \wedge a_{j} > a_{i}}(a_{j})+1
dpi=maxj⩽i−1∧aj>ai(aj)+1。
最终要求的最长递增子序列为
m
a
x
1
⩽
i
⩽
n
(
d
p
i
)
max_{1 \leqslant i \leqslant n}(dp_{i})
max1⩽i⩽n(dpi)
2.2.1 时间复杂度分析
对于每个 d p i dp_{i} dpi都需要在 [ 1 , i − 1 ] [1,i-1] [1,i−1]中寻找最优解,所以时间复杂度为 O ( n 2 ) \mathcal{O}(n^{2}) O(n2)。
2.2.2 实现
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=5e3+10;
struct Node{
int l,w;
}a[N];
int dp[N];
bool operator<(const Node& a,const Node& b){
return a.l<b.l||a.l==b.l&&a.w<b.w;
}
int main(){
int T;scanf("%d",&T);
while(T--){
int n;scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d %d",&a[i].l,&a[i].w);
sort(a,a+n);
int res=0;
for(int i=0;i<n;i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(a[j].w>a[i].w){
dp[i]=max(dp[i],dp[j]+1);
}
}
res=max(res,dp[i]);
}
printf("%d\n",res);
}
return 0;
}