题目
思路
虽然原题的表述是找到最小的奶牛集合,实则在求 最长的上升子序列。根据题意,不在集合中的奶牛的相对位置不会发生改变。而最后的序列是有序的,所以这些没有被选中的奶牛是天生有序的——也就是上升的。未被选中的越多,被选中的就越少,所以是在求最长上升子序列 L I S LIS LIS 。
至于为什么一定能完成?因为逆序对是在减少的。
那么字典序第 k k k 大该怎么处理?可以考虑将 未选中集合(也就是最长上升子序列)一位一位的确定。
假设有两个最长上升子序列,分别以 a a a 和 b b b 开头,不妨设 a > b a>b a>b 。
- 选择序列 a a a ,对应的是集合 [ 1 , a ) ∪ ( a , . . . ] [1,a)∪(a,...] [1,a)∪(a,...] 。
- 选择序列 b b b ,对应的是集合 [ 1 , b ) ∪ ( b , . . . ] [1,b)∪(b,...] [1,b)∪(b,...] 。
将前 b b b 位排列出来,也就是
- 序列 a a a 对应 1 , 2 , 3 , . . . , b − 1 , b 1,2,3,...,b-1,b 1,2,3,...,b−1,b 。
- 序列 b b b 对应 1 , 2 , 3 , . . . , b − 1 , b + 1 1,2,3,...,b-1,b+1 1,2,3,...,b−1,b+1 。
注意到二者从第 b b b 位开始出现差异,且第 b b b 位上 a a a 的字典序小于 b b b 的字典序,故 a a a 的字典序小于 b b b 的字典序。由此,我们得到:
结论一:开头大的字典序小
那么我们只需要把最长上升子序列按照第一位从大到小进行排序,然后寻找第 k k k 大在哪个子序列开头中。开头确定后,在 开头后面的位置中 寻找长度小 1 1 1 的上升子序列,用同样的判断确定第二位。直到整个未选中集合确认为止。
既然是按照第一位排序,就用 d p i dp_i dpi 表示以第 i i i 个数开头的最长上升子序列吧(下面的最长上升子序列定义同)!
可是这样还要排序,不是很麻烦吗?
考虑两个同样长度的最大上升子序列,其开头的 下标 分别为 a a a 和 b b b (原数组为 S S S )。不妨设 a < b a<b a<b 。
若 S a < S b S_a<S_b Sa<Sb ,则将 S a S_a Sa 加入以 S b S_b Sb 开头的上升子序列,将会得到一个新的上升子序列,且长度大于以 S a S_a Sa 开头的原序列,与原序列为最长上升子序列矛盾。故 S a < S b S_a<S_b Sa<Sb 一定不成立。
既然 S a ≥ S b S_a\ge S_b Sa≥Sb ,根据结论一,以 S a S_a Sa 开头的最长上升子序列就比 S b S_b Sb 开头的序列小。所以有:
结论二:开头靠前的字典序小
那么我们从前往后枚举开头,放进数组便 自然有序!
有个小细节,就是个数可能过大(最大为 ( n n 2 ) {n\choose{\frac{n}{2}}} (2nn) , n n n 是 100000 100000 100000 ,作者能力有限,无法计算)。但超过 k k k 个,就不再重要了,我们只需字典序第 k k k 大的。故个数始终与 k k k 取较小值。
代码
以前的 丑陋的 代码。
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
struct Node{
int len; long long cnt;
};
const int MAXN=100000;
int n, a[MAXN+2]; long long k;
Node dp[MAXN+2];
Node BIT[MAXN+2];
inline int lowbit(const int &x){ return x&-x; }
Node Query(int id){
Node ans; ans.len = 0, ans.cnt = 1;
for(; id; id-=lowbit(id))
if(ans.len < BIT[id].len)
ans = BIT[id];
else if(ans.len != BIT[id].len)
continue;
else
ans.cnt = min(ans.cnt+BIT[id].cnt,k);
return ans;
}
void Add(int id,Node &v){
for(; id<=n; id+=lowbit(id))
if(BIT[id].len < v.len)
BIT[id] = v;
else if(BIT[id].len != v.len)
continue;
else
BIT[id].cnt = min(BIT[id].cnt+v.cnt,k);
}
bool chose[MAXN+2];
vector<int> head[MAXN+2];
int main(){
cin >> n >> k;
for(int i=1; i<=n; ++i)
scanf("%d",&a[i]);
for(int i=n; i; --i){
dp[i] = Query(n-a[i]);
++ dp[i].len;
Add(n+1-a[i],dp[i]);
}
for(int i=1; i<=n; ++i)
head[dp[i].len].push_back(i);
int MaxLen = Query(n).len;// of LIS
for(int len=MaxLen,last=1; len; --len) // 本质是枚举第几位
for(auto i:head[len])
if(dp[i].cnt < k)
k -= dp[i].cnt;
else{
chose[a[i]] = 1; // 将当前这一位设定为没有被选中的
for(; last<=i; ++last)
dp[last].cnt = 0; // 确保是在当前位的后面寻找下一位
break;
}
printf("%d\n",n-MaxLen);
for(int i=1; i<=n; ++i)
if(not chose[i])
printf("%d\n",i);
return 0;
}
部分参考了洛谷题解