LIS数量统计
设f[i]代表以第i个数字为开头的LIS长度,g[i]代表方案数
转移时从后往前 f[i]=f[i+1···n]中的最大值 g[i]为最大长度的方案数的和
用树状数组维护即可,若需要维护以第i个数字为结尾的最长的LIS长度,正的做即可 (代码和下面题解和在一起了,感觉还算简单)
P5156 [USACO18DEC]Sort It Out 题解
题目中的排序方式类似于快速排序,不会改变其他数的相对位置
类似于之前模拟赛中的卡片重新插入排序类似的是,求出其最长子序列的长度,然后由n减去它即是集合大小
但本题还需要我们求出字典序第K小的集合,即求字典序第K大的LIS,但这样会给下面的操作带来麻烦(不转也可以,但我看不懂不转的代码) 将a(i)=x转化为b(x)=i 即由下标映射权值转化为权值映射下标,则字典序第K大转换为下标字典序第K大方便接下来的操作(保证vector内有序(虽然听说不转换vector内依旧有序但我不会证明))
然后我们用vector(或链表)来维护长度为 k 的LIS的开头有哪些,然后从长度k到1进行枚举
若以当前枚举到的数字为开头的LIS方案数比k小则这个数字不能选(因为选了之后就算是最大的字典序也无法达到k)若比k大则往下选。
注意:当前枚举到的数不能比之前枚举到的数还要小,需要特判
这题由于权值和下标老是转来转去,故将详细的解释放在代码的注释里(其实是我自己理解能力低下)
注意:代码中的下标和权值为了方便理解,标的是依据b的下标和权值。实际上b的下标是a的权值,b的权值是a的下标
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define lob lower_bound
#define upb upper_bound
#define mk make_pair
#define pii pair<int,int>
#define fst first
#define scd second
using namespace std;
const ll INF=1e18+5;
const int MAXN=1e5+5;
int n,a[MAXN];
ll K;
struct node{int len;ll num;node(int lenn=0,ll numm=0){len=lenn;num=numm;}}c[MAXN],f[MAXN];
void upd(node &x,node y){
if(x.len>y.len)return;
if(x.len<y.len)x=y;
else x.num=min(INF,x.num+y.num);
}
inline int lb(int x){return x&(-x);}
void add(int p,node v){
for(;p>0;p-=lb(p))upd(c[p],v);
}
node ask(int p){
node ans=node(0,1);
for(;p<=n;p+=lb(p))upd(ans,c[p]);
return ans;
}
vector<int> s[MAXN];
bool ch[MAXN];
int main() {
ios::sync_with_stdio(0);/*syn加速*/
cin>>n>>K;
for(int i=1;i<=n;++i)cin>>a[i];
for(int i=n;i>=1;--i){
f[i]=ask(a[i]+1);
f[i].len++;
add(a[i],f[i]);
}
for(int i=1;i<=n;++i) s[f[i].len].pb(i);//i递增,字典序递减(否则不可能长度相同)
for(int len=ask(1).len,R=1;len>=1;--len){
for(int i=0;i<(int)s[len].size();++i){
int v=s[len][i];
if(f[v].num<K)K-=f[v].num;
else {
ch[a[v]]=1;
while(R<=v)f[R].num=0,R++;
break;
}
}
}
cout<<n-ask(1).len<<endl;
for(int i=1;i<=n;++i)if(!ch[i])cout<<i<<endl;
return 0;
}