[USACO18DEC]Sort It Out P 题解

LIS 计数

Statement

[USACO18DEC]Sort It Out P - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Solution

其实这道题最精髓的一步在于转化题目

我们发现题目中”按顺序排好“这个操作正向模拟非常困难,我们需要寻找更简洁的充要条件

我们直接去看终态,因为终态需要是有序的,我们不妨直接把这个操作理解为把数字 \(i\) ,放到 \(i\) 号位置

那么这样,判定就变成了选择一个集合,使得集合中的数字放到应该放的位置,形成的序列是否升序

正确性:”按顺序排好“在左边比她小,右边比她大的时候停止,如若集合外的数都在应该在的位置,那么当然会在应该放的位置停止

如若存在有一些数的顺序混乱,显然我的排序操作不会影响他们的相对顺序,所以 GG

也就是说,原来条件中成立的现在条件中也成立,原来条件中不成立的现在也不成立,所以是充要的

容易想到集合外的数应该构成上升子序列,求字典序第 \(k\) 小的满足题意的集合,就是求序列中字典序 第 \(k\) 大的最长上升子序列

沿用普通序列 \(k\) 大的求法,我们应当求出从每一个数开始的 LIS 个数

Luogu 题解区的大佬采用了更为优雅的 DP 方式,即记 \(f[i]\) 表示以值 \(i\) 为开头的 LIS ,然后重定义了一下加法,算出方案数

蒟蒻比较蠢,设 \(f[i]\) 表示以第 \(i\) 个位置为开头的 LIS ,容易用 BIT \(O(n\log n)\) 求出 \(f\)

然后考虑 \(g[i]\) 表示以第 \(i\) 个位置为开头的 LIS 的方案数怎么算(没有想到可以重定义加法/kk)

枚举 LIS 长度,有这样的式子

\[g[i]=\sum_{f[j]+1==f[i] \&\& j>i \&\& a[j]>a[i]} g[j] \]

有点吓人,不妨把 \(f[i]\) 相同的丢到同一个 vector 里面去,同一个 vector 里面下标单调递增

计算 \(vec[i]\) 时,可以二分出要查询的位置挂在 \(vec[i-1]\) 中处理 \(j>i\) 的条件,然后 BIT 处理 \(a[j]>a[i]\) 的条件

这样算出来还是 \(O(n\log n)\) ,容易发现本质和重定义加法然后和 \(f\) 一起算出来的效果相同

开始处理询问,应当从 LIS 大的开始

因为字典序的要求是对 \(a\) 的要求,所以我们把 vec 按 \(a\) 从小到大排序

那么,当我选择了 \(a[i]\) 后,下一个数 \(a[j]\) 应满足 \(j>i,a[j]>a[i]\) ,用两个变量限制一下就可以了

总复杂度 \(O(n\log n)\)

Code

#include<bits/stdc++.h>
#define lowbit(x) (-x&x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e5+5;

char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
ll read(){
    ll s=0,w=1; char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
    while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
    return s*w;
}

struct BITmx{
    int c[N];
    void add(int x,int v){for(;x<N;x+=lowbit(x))c[x]=max(c[x],v);}
    int ask(int x){int r=0;for(;x;x-=lowbit(x))r=max(r,c[x]);return r;}
}mx;
struct BITsum{
    ll c[N];
    void clear(int x){for(;x<N;x+=lowbit(x))c[x]=0;}
    void add(int x,ll v){for(;x<N;x+=lowbit(x))c[x]=min((ull)1e18,(ull)c[x]+v);}
    ll ask(int x){ll r=0;for(;x;x-=lowbit(x))r=min((ull)1e18,(ull)r+c[x]);return r;}
    //因为方案数可能爆 long long ,所以这里要和 1e18 取 min,由于运算过程中还是有可能爆 long long ,所以干脆 unsigned 一下 (调了 1h+/kk)
}sum;
int a[N],b[N],f[N],pos[N],n,m;
vector<int>vec[N],item[N];
ll g[N],k;
bool vis[N];

signed main(){
    n=read(),k=read();
    for(int i=1;i<=n;++i)
        a[i]=read(),b[a[i]]=i;
    for(int i=n;i>=1;--i)
        f[i]=mx.ask(n-a[i]+1)+1,
        mx.add(n-a[i]+1,f[i]),m=max(m,f[i]);
    for(int i=1;i<=n;++i)
        vec[f[i]].push_back(i);
    // for(int i=1;i<=m;++i,puts(""))
        // for(auto v:vec[i])cout<<v<<" ";
    for(auto v:vec[1])g[v]=1;
    for(int i=2;i<=m;++i){
        for(auto v:vec[i])
            pos[v]=upper_bound(vec[i-1].begin(),vec[i-1].end(),v)-vec[i-1].begin(),
            item[pos[v]].push_back(v);
        for(int j=vec[i-1].size()-1;~j;--j){
            sum.add(n-a[vec[i-1][j]]+1,g[vec[i-1][j]]);
            for(auto v:item[j])g[v]=sum.ask(n-a[v]+1);
        }
        for(auto v:vec[i-1])
            sum.clear(n-a[v]+1);
        for(auto v:vec[i])
            if(item[pos[v]].size())
                item[pos[v]].clear();
    }
    // for(int i=1;i<=n;++i)cout<<g[i]<<" ";puts("");
    // for(int i=1;i<=m;++i,puts(""))
    //     for(auto v:vec[i])cout<<pos[v]<<" ";
    for(int i=1;i<=m;++i)
        sort(vec[i].begin(),vec[i].end(),[](int x,int y){
            return a[x]<a[y];
        });
    int lim1=0,lim2=0;
    for(int i=m;i;--i){
        for(int j=vec[i].size()-1;~j;--j){
            if(vec[i][j]>lim1&&a[vec[i][j]]>lim2){
                if(g[vec[i][j]]<k)
                    k-=g[vec[i][j]];
                else{
                    vis[a[vec[i][j]]]=1;
                    lim1=vec[i][j],lim2=a[vec[i][j]];
                    break;
                }
            }
        }
    }
    printf("%d\n",n-m);
    for(int i=1;i<=n;++i)
        if(!vis[i])printf("%d\n",i);
    return 0;
}
posted @ 2022-04-24 15:20  _Famiglistimo  阅读(30)  评论(0编辑  收藏  举报