P5156 [USACO18DEC]Sort It Out

传送门

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值