题目大意:
给出一种n个不同数字的全排列,把他分割成多段严格递增的子序列,并输出分割后的序列.
例如1 4 2 3 5 6
按照递增 分为 1 2 3 5 6和4
题目分析:因为n有2*1e5那么大,所以暴力n^2明显不行了,不过可以在暴力的基础上通过二分查找使时间复杂度降为nlogn.
暴力方法,建立一个vector的数组ans[].里面存放不同段的子序列
从第一个元素开始扫起(设为a),该元素与ans[]中最后的元素(设为last)比较,a>last时说明递增,把a放到该段的后面即ans[i].push_back(a);
当a比所有的last都小时,建立一个新的段,并把a放到该段后面.
很明显上述方法最坏情况会o(n/2)^2爆炸.
优化的话,我们可以设一个新的数组last[],里面存放的事ans[]的末尾元素,这样在查找的时候我们可以用二分法加速.达到nlogn.
#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<map>
#include<algorithm>
#include<queue>
#include<set>
#include<cstdio>
#include<functional>
#include<iomanip>
#include<cmath>
#include<stack>
#define inf 1<<30
#define mod 1030
#define esp 1e-8
using namespace std;
typedef long long LL;
const int maxn = (int)(2 * 1e5) + 10;
vector<int>ans[maxn];
int num[maxn], last[maxn];
bool vis[maxn];
void init(int n) {
for (int i = 0; i <= n; i++) {
ans[i].clear();
vis[i] = 0;
last[i] = 0;
}
}
int find(int size, int key)
{
int first = 0, middle;
int half, len;
len = size;
while (len > 0) {
half = len >> 1;
middle = first + half;
if (last[middle] >= key) {//因为我存last时是倒序,所以分段方法要反过来
first = middle + 1;
len = len - half - 1;
}
else
len = half;
}
return first;
}
int main() {
int n;
while (scanf("%d", &n) == 1) {
init(n);
for (int i = 0; i < n; i++) scanf("%d", &num[i]);
int cnt = 0;
for (int i = 0; i < n; i++) {
bool flag = false;
if (i == 0) {
ans[cnt].push_back(num[i]);
last[cnt++] = num[i];
continue;
}
int t = find(cnt, num[i]);
if (t == 0) {//num[i]比所有末尾都大
ans[0].push_back(num[i]);
last[0] = num[i];
}
else if (t == cnt) {//num[i]比所有末尾都小
last[cnt] = num[i];
ans[cnt++].push_back(num[i]);
}
else {
last[t] = num[i];
ans[t].push_back(num[i]);
}
}
for (int i = 0; i < cnt; i++) {
int l = ans[i].size();
for (int j = 0; j < l; j++) {
printf("%d", ans[i][j]);
printf("%c", j == l - 1 ? '\n' : ' ');
}
}
}
}