/*此题为一个女大佬教我的,%%%%%%%%%%%%*/
题目描述
给出1-n的两个排列P1和P2,求它们的最长公共子序列。
输入输出格式
输入格式:
第一行是一个数n,
接下来两行,每行为n个数,为自然数1-n的一个排列。
输出格式:
一个数,即最长公共子序列的长度
朴素版的lis是O(N ^ 2)的做法,这里就不在给出;当数据大时很容易被卡,通过二分优化 + 贪心可以优化成为O(NlogN),首先介绍两个函数:
lower_bound( )和upper_bound( )是利用二分查找的方法在一个有序的数组中进行查找的。
当数组是从小到大时,
lower_bound( begin,end,num):表示从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,找到数字在数组中的下标。
upper_bound( begin,end,num):表示从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,找到数字在数组中的下标。
当数组是从大到小时,我们需要重载lower_bound()和upper_bound();
struct cmp{bool operator()(int a,int b){return a>b;}};
lower_bound( begin,end,num,cmp() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num,cmp() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
b[i] 表示长度为i的上升子序列的最后一个数的最小值,如果a[i] > b[i] 则显然子序列的长度加1;否则找到找到第一个比它大的值将其替换,最终可以找到lis的长度;
/*大佬们可以手写二分...*/
#include <bits/stdc++.h> using namespace std; #define ll long long #define INF 0x3f3f3f3f #define MAXN 1000010 #define MAXM 5010 inline int read() { int x = 0,ff = 1;char ch = getchar(); while(!isdigit(ch)) { if(ch == '-') ff = -1; ch = getchar(); } while(isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); } return x * ff; } int n,ans = 1,a[MAXN],b[MAXN]; int main() { n = read(); for(int i = 1;i <= n;++i) a[i] = read(); b[1] = a[1]; for(int i = 2;i <= n;++i) { if(b[ans] < a[i]) b[++ans] = a[i]; else b[lower_bound(b + 1,b + ans + 1,a[i]) - b] = a[i]; } printf("%d\n",ans); return 0; }
手写二分如下:
#include <bits/stdc++.h> using namespace std; #define MAXX 301000 int n; int a[MAXX], f[MAXX]; int top = 0; void find(int k) { int left = 1, right = top; while(left + 1 < right) { int mid = (left + right) / 2; if(f[mid] >= k) { right = mid; } else if(f[mid] < k) left = mid; } // cout << left << ' ' << right << ' ' << top << endl; if(f[left] > k) f[left] = k; else if(f[right] > k && f[left] <= k) f[right] = k; } int main() { memset(f, 0, sizeof(f)); scanf("%d", &n); for(int i = 1; i <= n; ++i) { scanf("%d", &a[i]); if(a[i] > f[top]) { f[++top] = a[i]; } else if(a[i] < f[top]) { find(a[i]); } } printf("%d", top); return 0; }
加强版:
描述 Description
有N个整数,输出这N个整数的最长上升序列、最长下降序列、最长不上升序列和最长不下降序列。
输入格式 Input Format
第一行,仅有一个数N。 N<=700000
第二行,有N个整数。 -10^9<=每个数<=10^9
输出格式 Output Format
第一行,输出最长上升序列长度。
第二行,输出最长下降序列长度。
第三行,输出最长不上升序列长度。
第四行,输出最长不下降序列长度。
这岂不是很显然:
#include <bits/stdc++.h> using namespace std; #define ll long long #define INF 0x3f3f3f3f #define MAXN 1000010 #define MAXM 5010 inline int read() { int x = 0,ff = 1;char ch = getchar(); while(!isdigit(ch)) { if(ch == '-') ff = -1; ch = getchar(); } while(isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); } return x * ff; } int n,ans1,ans2,ans3,ans4,a[MAXN]; int b1[MAXN],b2[MAXN],b3[MAXN],b4[MAXN]; struct cmp{bool operator()(int a,int b){return a>b;}}; int main() { n = read(); for(int i = 1;i <= n;++i) a[i] = read(); ans1 = ans2 = ans3 = ans4 = 1; b1[1] = b2[1] = b3[1] = b4[1] = a[1]; for(int i = 2;i <= n;++i) { if(a[i] > b1[ans1]) b1[++ans1] = a[i]; else b1[lower_bound(b1 + 1,b1 + ans1 + 1,a[i]) - b1] = a[i]; if(a[i] < b2[ans2]) b2[++ans2] = a[i]; else b2[lower_bound(b2 + 1,b2 + ans2 + 1,a[i],cmp()) - b2] = a[i]; if(a[i] <= b3[ans3]) b3[++ans3] = a[i]; else b3[upper_bound(b3 + 1,b3 + ans3 + 1,a[i],cmp()) - b3] = a[i]; if(a[i] >= b4[ans4]) b4[++ans4] = a[i]; else b4[upper_bound(b4 + 1,b4 + ans4 + 1,a[i]) - b4] = a[i]; } printf("%d\n%d\n%d\n%d\n",ans1,ans2,ans3,ans4); return 0; }