木棍加工——题解
- 题目:
Problem
一堆木头棍子共有n根,每根棍子的长度和宽度都是已知的。棍子可以被一台机器一个接一个地加工。机器处理一根棍子之前需要准备时间。准备时间是这样定义的:
第一根棍子的准备时间为1分钟;
如果刚处理完长度为L,宽度为W的棍子,那么如果下一个棍子长度为Li,宽度为Wi,并且满足WiL≥Li,W≥Wi,这个棍子就不需要准备时间,否则需要1分钟的准备时间;
计算处理完n根棍子所需要的最短准备时间。比如,你有5根棍子,长度和宽度分别为(4, 9),(5, 2),(2, 1),(3, 5),(1, 4),最短准备时间为2(按(4, 9)、(3, 5)、(1, 4)、(5, 2)、(2, 1)的次序进行加工)。
Input Data
第1行是一个整数n。
第2行是2n个整数,分别是L_1,W_1,L_2,w_2,...,L_n,W_n。
Output Data
仅一行,一个整数,所需要的最短准备时间。
5 4 9 5 2 2 1 3 5 1 4
2
Data Limit
n≤5000
L和W的值均不超过10000。
Problem Source
LuoGu1233
- 思路:
- 题目第一眼看去是根据木棍长度贪心,但DP似乎更保险,就先根据长度sort一遍,之后就只需要考虑宽了。
所以,只需用结构体来记录,之后设置cmp来sort,初始化完成。 - 之后思考,如何才能解决宽度问题。其实就只要把它们分组,每组为上升序列就好了。为了实现这一点,最早思路是,给数组做多次最长上升子序列,每次找到长度,计数加1,然后删去这些数。这样似乎可行,但有些麻烦。
因此,牵扯到dilworth定理。简单来说,就是:下降子序列的最小划分等于最长不下降子序列的长度。(至于证明,百度百科里有,这里不做解释——也不懂。。。)
我们就可以只用做一个最长下降子序列,长度就是答案!
这里用到两个小技巧:1.排序从大到小排,所以只用做最长上升子序列即可。2.lower_bound - 说说最长上升子序列把。这里我们采用二分思想,经典的DP(你会发现很多题这个模板是可以套的),简化下来是这样:用一个 f 数组,表示当前找到的最长上升子序列,需要维护其升序。用一个cnt,表示这个序列的长度。每次循环,先看是否比f末尾(最大的数)还要大。如果是,那么可以插入到这个序列的末尾,长度加1并记录。如果不行,就用lower_bound——这是algorithm里的一个好东西,它可以返回在数组中第一个>=查找数的位置,详见百度百科。利用它,可以快速二分,使代码非常简短。
- 代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 inline int read(){ 4 int x=0,f=1;char ch=getchar(); 5 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 6 while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} 7 return x*f; 8 }//快读,这里有5000数据,可以压缩时间 9 const int MAXN=5000+5; 10 struct lll{ 11 int l,w; 12 }a[MAXN]; 13 int n,f[MAXN],cnt;//cnt计数 14 bool cmp(lll x,lll y) 15 { 16 if(x.l==y.l) return x.w>y.w; 17 return x.l>y.l; 18 }//先看长度再看宽度 19 int main() 20 { 21 n=read(); 22 for(int i=1;i<=n;i++) 23 a[i].l=read(),a[i].w=read(); 24 sort(a+1,a+n+1,cmp);//排序 25 for(int i=1;i<=n;i++)//最长上升子序列 26 { 27 if(a[i].w>f[cnt]) f[++cnt]=a[i].w; 28 else *lower_bound(f+1,f+1+cnt,a[i].w)=a[i].w; 29 } 30 printf("%d\n",cnt);//把计数输出即可 31 return 0; 32 }