思路:
一:
-
这是一道经典的动归题目,经典在它可以牵引出 Dilworth定理、LIS(Longest Increasing Subsequence)。
-
先说 Dilworth:Dilworth定理包含如下知识:偏序集、链、反链。Dilworth 包括两个对偶定理:
- 最大链长度 = 最少反链划分数。
- 最大反链长度 = 最少链划分数。
-
那如何使用该定理呢?非常简单。以本题为例:求最少的不上升(>=)链划分数。首先:链和反链如何定义并不需要很深的理解,我们只需要定义一种关系,在本题中,此关系为:既前且高(=),时间上不会有重叠,高度上相当于 “高于等于”;而最少划分和最大长度是紧密绑定不能更改的。如此,本题即可等价于求最大的上升(<)反链长度。十分对偶。
-
将 Dilworth定理 实体化为公式:
设某种关系为 &,它的反关系为 %(例如 >= 和 <,注意严格和不严格的对偶)- 求 具有&关系链 的最少划分,等于求 具有%关系链 的最大长度。
- 求 具有%关系链 的最少划分,等于求 具有&关系链 的最大长度。
-
通过 Dilworth 公式,我们可以轻易发现本题的一种解法:直接求出所给序列的 LIS。
二:
- 又可根据一结论:暴力找具有某关系的子链,找到子链的数目即为最少的该关系的链划分数。根据本结论,可以给出本题的第二种解法。
三:
- 我们已经知道,第一种解法(Dilworth)实质上需要求的就是 LIS。那么 LIS 又有几种解法呢?
- N^2:三角形动态规划。
- NlgN:用到二分、顶替的思想。详见再见~雨泉的BLOG。我的代码中采用了非常非常简单的,使用了STL的二分求 LIS的方法,来自BIGKAKA的BLOG。感谢。
代码:
- 三角形朴素动归,求LIS:46ms 1428kB
//46ms 1428kB
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 5005;
int N;
int H[maxn];
int dp[maxn];
int main(){
while(cin>>N){
for(int i=1;i<=N;i++)
dp[i] = 1;
int MAX = 0;
for(int i=1;i<=N;i++)
scanf("%d" , H + i);
for(int i=1;i<=N;i++)
for(int j=1;j<i;j++)
if(H[i] > H[j])
dp[i] = max(dp[i] , dp[j] + 1);
for(int i=1;i<=N;i++)
MAX = max(dp[i] , MAX);
cout<<MAX<<endl;
}
return 0;
}
- 特殊结论:0ms 1424kB
//0ms 1424kB
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 5005;
int N;
int H[maxn];
bool vis[maxn];
int ans;
void INIT(){
ans = 0;
memset(vis , 0 , sizeof(vis));
return ;
}
int main(){
while(cin>>N){
INIT();
for(int i=1;i<=N;i++)
scanf("%d" , H + i);
while(true){
int MAX = INF;
for(int i=1;i<=N;i++){
if(!vis[i] && MAX >= H[i]){
MAX = H[i];
vis[i] = true;
}
}
if(MAX == INF)
break;
ans++;
}
cout<<ans<<endl;
}
return 0;
}
- LIS的NlgN:15ms 1444kB
//15ms 1444kB
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 5005;
int N;
int H[maxn];
int dp[maxn];
void INIT(){
memset(dp , INF , sizeof(dp));
return ;
}
int main(){
while(cin>>N){
INIT();
for(int i=1;i<=N;i++)
scanf("%d" , H + i);
for(int i=1;i<=N;i++)
*lower_bound(dp , dp+N , H[i]) = H[i];
cout<<lower_bound(dp , dp+N , INF) - dp<<endl;
}
return 0;
}
二刷:
- N^2:46ms 1416kB
//46ms 1416kB
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 30005;
int N;
int w[maxn];
int dp[maxn];
int main(){
while(cin>>N){
for(int i=1;i<=N;i++)
cin>>w[i];
int MAX = 0;
for(int i=1;i<=N;i++){
dp[i] = 1;
for(int j=1;j<=i;j++)
if(w[j] < w[i])
dp[i] = max(dp[i] , dp[j] + 1);
MAX = max(MAX , dp[i]);
}
cout<<MAX<<endl;
}
return 0;
}