1. 程式人生 > >題解-USACO18DEC Sort It Out

題解-USACO18DEC Sort It Out

Problem

洛谷5156

題意概要:給定一個長為\(n\)的排列,可以選擇一個集合\(S\)使這個集合內部元素排到自己在整個序列中應該在的位置(即對於集合\(S\)內的每一個元素\(i\),使其排到第\(i\)號位置,使得整個排列在排序後為上升序列。求滿足這樣條件的,且集合大小最小的集合中字典序第\(k\)小的集合(可能總結不到位,看連結裡的吧)

\(n\leq 10^5\)

Solution

不難發現出題人費盡心思寫的題面就是在強烈暗示選取一個集合等價於將這個集合內所有元素排到自己該處於的位置(即元素\(i\)應該在位置\(i\)

進一步發現集合內的元素很自覺的到了正確的位置,而集合外的元素不會更改相對位置,為了使最終整個排列單調遞增,即要求集合外的元素必須滿足在一開始就是單調遞增的

求字典序第\(k\)小的滿足題意的集合,取反一下,就是求序列中字典序第\(k\)大的最長上升子序列

(至此題目模型轉化完成)


現在目標為求字典序第\(k\)大的最長上升子序列

在繼續之前建議先將最長遞增子序列的數量解決:

設定\(f_i\)表示以權值為\(i\)結尾的\(LIS\)的長度和數量,則權值\(x\)\(f_1...f_{x-1}\)間轉移,用樹狀陣列維護字首最大值和數量即可\(O(n\log n)\)解決


利用上面這題的思想,已經可以求得以每個元素開頭的\(LIS\)長度和數量

這題和上面這題雖有不同,但本質類似,想想一般線段樹求第 \(k\) 大的過程,正是依次確定每一層的節點,而為了確定每一層的節點,就需要用到所有節點子樹和

同理,假設當前要求的序列的\(LIS\)長度為 \(t\),則求第\(k\)\(LIS\)的一個思想就是先確定第\(1\)個數,再在確定第\(1\)個數的基礎上確定下一個數……以此類推可以最終確定\(LIS\)的每一位

細化一下,就是將所有可能作為\(LIS\)的第\(i\)位的數 放進第\(i\)個vector裡,將每個vector內部進行元素排序,在確定每一位時從大到小確定,若當前值後面牽扯的\(LIS\)數量小於\(k\),則將\(k\)減去這個數量然後檢查下一個值,否則將這個值確定下來並開始確認下一位

(值得注意的一點,若求\(LIS\)\(i\)層選定了位置\(R\)的元素,則接下來都不能選擇\(R\)

左邊的元素)

Code

關於程式碼中的一些疑問:

  • 我沒有用vector,而是使用鏈式前向星替代
  • 由於每個vector裡的元素一定是按照位置遞增而權值遞減的,所以並不需要排序
  • 很多人用線段樹,而這題只需要樹狀陣列即可
  • 在求完以每個點開始的\(LIS\)後樹狀陣列就沒用了,可以節約大量時間
#include <bits/stdc++.h>
typedef long long ll;

inline void read(int&x){
    char c11=getchar();x=0;while(!isdigit(c11))c11=getchar();
    while(isdigit(c11))x=x*10+c11-'0',c11=getchar();
}

const int N=101000;
struct Edge{int v,nxt;}e[N];
int a[N],chs[N],head[N];
int n,_;ll k;

const ll lim=1e18;

struct node{
    int v;ll c;
    inline node(){}
    friend inline void operator + (node&A,const node B){
        if(A.v<B.v)A.v=B.v,A.c=B.c;
        else if(A.v==B.v)A.c=std::min(lim,A.c+B.c);
    }
}d[N],g[N],cl;

#define lb(x) (x&(-x))

inline node qy(int x){node p=cl;for(x;x<=n+1;x+=lb(x))p+d[x];return p;}
inline void add(int x,node y){for(;x;x-=lb(x))d[x]+y;}
inline void ae(int u,int v){e[++_].v=v,e[_].nxt=head[u],head[u]=_;}

int main(){
    scanf("%d%lld",&n,&k);
    for(int i=1;i<=n;++i)read(a[i]);
    cl.c=1,add(n+1,cl),cl.c=0;
    for(int i=n;i;--i){
        g[i]=qy(a[i]);
        ++g[i].v;
        add(a[i],g[i]);
    }
    for(int i=n;i;--i)ae(g[i].v,i);
    for(int stp=qy(1).v,R=0;stp;--stp)
    for(int i=head[stp],v;i;i=e[i].nxt){
        v=e[i].v;
        if(g[v].c<k)k-=g[v].c;
        else {
            chs[a[v]]=true;
            while(R<v)g[R++]=cl;
            break;
        }
    }
    printf("%d\n",n-qy(1).v);
    for(int i=1;i<=n;++i)
        if(!chs[i])printf("%d\n",i);
    return 0;
}