7-28 快乐的尽头
分数 17
快乐风男面前有n个兵,呈线性排列编号为1~n,每个小兵携带a[i]个金币1<=i<=1e5,为了体现快乐的极致,快乐风男知道了每个小兵携带的金币,快乐的他E往无前(也就是说他不会回头),但是快乐的他每次e的小兵的金币都严格递增,为了他能快乐多一点,请你给出他e兵的方案(上升子序列下标 如样例最长上升子序列为1 2 3 4 ,你需要输出其对应的下标1 2 3 5),让他e的兵越多越好,如果有多个快乐方案,给出字典序最小1<=n<=100000
(由于原数据未分组,现在不需要读入T,即每个数据一个案例)
输入格式:
第一行输入n表示有n个小兵 第二行为每个小兵的携带金币
输出格式:
第一行给出快乐风男可以e的最大兵数,第二行给出最佳方案
输入样例:
5
1 2 3 3 4
输出样例:
4
1 2 3 5
代码长度限制
16 KB
时间限制
1000 ms
内存限制
256 MB
思路 单调栈+二分+二分回溯
复杂度\(O(nlogn)\)
可以看看证明
这里只写以下如何回溯,我们用record数组记录,由于末端数的性质保持单调递减,下标因为从1~n访问,所以单调递增,为了保证下标的字典序最小,我们从尾端向前递推,因为尾端能够确定最小的数索引尾最后一个并且保证是最长上升子序列,后面我们就找到第一个小于它的数,来确保是最小下标,这里就可以用二分来搜以下。
Code
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int a[N], stk[N];
vector<int> record[N];
/*
record小标记录的数单调递减, 下标也单调递增
我们想要满足条件的最小下标
*/
int main() {
int n, len = 0;
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
stk[0] = -1;
for(int i = 1; i <= n; i ++) {
int l = 0, r = len;
while(l < r) {
int mid = l + r + 1 >> 1;
if(stk[mid] < a[i]) l = mid;
else r = mid - 1;
}
len = max(l + 1, len);
record[l + 1].push_back(i);
stk[l + 1] = a[i];
}
cout << len << "\n";
for(int i = 1; i <= len; i ++) {
cout << "len: " << i << "->";
for(int j = 0; j < record[i].size(); j ++) {
cout << record[i][j] << " \n"[j == (int) record[i].size() - 1];
}
}
vector<int> curr;
for(int i = len; i >= 1; i --) {
if(i == len) curr.push_back(record[i][0]);
else {
int pos = (int) record[i].size() - 1;
while(pos >= 0 && a[record[i][pos]] >= a[curr.back()]) pos --;
curr.push_back(record[i][pos]);
}
}
for(int i = (int) curr.size() - 1; i >= 0; i --) {
cout << curr[i] << " \n"[i == 0];
}
}