[bzoj1046][HAOI2007]上升序列
一个有意思的题。
很显然,这是LIS问题的加强版。
对于每一个询问,我们从前到后检查每一个元素,如果f[i]>=x那么输出,然后x--。如果x最终为零,那么说明有解。
证明:
首先,由于我们是从前考虑的每一个元素,所以保证了字典序最小。
其次,因为如果对于一个元素i|f[i]>x,那么从i后面一定至少能够找到x个元素使得构成一个长度为x的序列。
考虑到字典序的定义,这样贪心一定是最优的。
我们处理LIS时,令g[i]为使f[j]==i的最大j,这样可以用二分查照优化转移,复杂度从O(n2)降到了O(nlogn)
当然,这个题目不优化,直接使用O(n2)的算法也完全可以卡过。
下面上代码。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 10001;
struct num {
int value, pos;
bool operator < (const num& b) const {
return this->pos<b.pos;
}
} a[maxn];
int n, m, Ma;
int f[maxn];
void getlis() {
f[n-1] = 1;
for(int i = n-2; i >= 0; i--) {
int ans = 0;
for(int j = i+1; j < n; j++) {
if(a[j].value>a[i].value && f[j] > ans){
ans = f[j];
}
}
f[i] = ans+1;
}
}
void getlis2() {
f[n-1] = 1;
int g[maxn]; //define g[i] to be the max j that let f[j] = i
memset(g, -1, sizeof(g));
g[0] = 0x3f3f3f;
for(int i = n - 1; i >= 0; i--) {
int ans = 0;
int L = 0;
int R = n;
while(L < R) {
if(R-L <= 1) break; //二分查找最小的比x大的元素
int mid = (L+R)/2;
if(g[mid] > a[i].value) L = mid;
else R = mid;
}
ans = L;
f[i] = ans+1;
g[ans+1] = max(g[ans+1], a[i].value);
}
}
void solve() {
sort(a, a+n);
scanf("%d", &m);
while(m--) {
int l;
scanf("%d", &l);
int x = l;
int cnt = 0;
int b[maxn];
int lastpos = 0;
for(int i = 0; i < n; i++) {
if(f[a[i].pos] >= x && a[i].value> lastpos) {
b[cnt++] = a[i].value;
x--;
lastpos = a[i].value;
}
}
if(cnt < l-1) printf("%s\n", "Impossible");
else {
for(int i = 0; i < l; i++) {
printf("%d", b[i]);
if(i!=l-1) putchar(' ');
}
printf("\n");
}
}
}
int main() {
scanf("%d", &n);
for(int i = 0; i < n; i++) {
int x;
scanf("%d", &x);
a[i] = {x, i};
Ma = max(Ma, x);
}
getlis2();
// for(int i = 0; i < n; i++) cout << f[i] << ' ';
solve();
}