4.17记录 LIS其二
输出最终结果的LIS。
思路相比传统的LIS来讲,一是原来的队列二分不那么好用了(队列模拟最终得出来的那个所谓LIS实际并不是真实的情况),二是需要加一点东西来存这个队列,也就是链表。
建议熟读并背诵。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int head[100001],a[100001];
void find(int x){
if(~head[x]) find(head[x]);
printf("%d ",a[x]);
}
int main(){
int n = 0,i,j,rear,telipu = -1,dp[100001];
memset(head,-1,sizeof(head));
while(scanf("%d",&a[++n]) != EOF);
--n;
for(i = 1;i <= n;i++) dp[i] = 1;
for(i = 2;i <= n;i++){
for(j = 1;j < i;j++){
if(a[j] < a[i] && dp[j] + 1 > dp[i]){
dp[i] = dp[j] + 1;
head[i] = j;
if(telipu < dp[i]){//维护一个最大值
telipu = dp[i];
rear = i;
}
}
//dp[i] = max(dp[i],dp[j]);
}
}
printf("max=%d\n",telipu);
find(rear);
return 0;
}
除此之外,还有一个导弹拦截问题。这题可以队列模拟了,不过二分比较讲究。
这题直接一分为二(源代码中我也加上了分隔),前半段是最长不上升子序列,find找的是第一个不大于它的数;后半段是最长上升子序列,find找的是第一个不小于它的数。所以这两次替换l = mid + 1的条件是不一样的,一次是大于等于,一次是小于。这和别的二分处理方式是一样的,看最后两个数的取法决定到底带不带等。
总之感觉这题考的不是贪心也不是DP而是二分…
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int Q[100001],a[100001],rear;
int find(int x){
int l = 1,r = rear,mid;
while(l < r){
mid = (l + r) >> 1;
if(Q[mid] < x) r = mid;
else l = mid + 1;
}
return l;
}
int anfind(int x){
int l = 1,r = rear,mid;
while(l < r){
mid = (l + r) >> 1;
if(Q[mid] < x) l = mid + 1;
else r = mid;
}
return l;
}
int main(){
int i,j,k,n = 0;
Q[0] = 2e9;
while(scanf("%d",&a[++n]) != EOF);
--n;
for(i = 1;i <= n;i++){
if(Q[rear] >= a[i]) Q[++rear] = a[i];
else{
k = find(a[i]);
Q[k] = a[i];
}
}
printf("%d\n",rear);
//--------------------------------------------------
memset(Q,0,sizeof(Q));
rear = 0;
for(i = 1;i <= n;i++){
if(Q[rear] < a[i]) Q[++rear] = a[i];
else{
k = anfind(a[i]);
Q[k] = a[i];
}
}
printf("%d\n",rear);
return 0;
}