Try   HackMD

CSES 2215

題目

  • 構造一個

    1..n 的排列使得他的 最長單調子序列 恰好長度為
    k

  • 最長單調子序列 是指這個子序列單調遞增或單調遞減

想法

LIS
longest increasing subsequnce

LDS
longest decreasing subsequnce

觀察

  • ans=max{len(LIS)len(LDS)

  • 發現說要想使答案控制得很好需要使得

    len(LIS)
    len(LDS)
    取得平衡

  • 例如

    n=9,k=3 那我必須使得
    len(LIS) = len(LDS) = 3

    • 使得

      LIS
      LDS
      不互相影響

    • [3,2,1,6,5,4,9,8,7]

    • 紅色為其中一個

      LIS 底線為
      LDS

證明

proof:

k<n 無解

  • 假設存在一個長度
    n=k2+1
    的排列
    p
    ,它的
    LIS
    LDS
    長度皆最多為
    k
    • 此時
      k<n
  • ai=
    ai
    結尾的
    LIS
    長度
  • bi=
    bi
    結尾的
    LDS
    長度
  • 對於所有
    i
    1ai,bik
    (我們的假設)
  • 因為
    (ai,bi)
    k2
    種可能 (籠子)
  • 但目前
    i=1..k2+1
    k2+1
    種 (鴿子)
  • 可由鴿籠原理得知必存在兩數
    i,j
    使
    ai=aj,bi=bj,(i<j)

鴿籠原理

  • 若有
    n
    個籠子和
    n+1
    隻鴿子,所有的鴿子都被關在鴿籠裡,那麼至少有一個籠子有至少
    2
    隻鴿子。
  • 那接著分以下

    case:

    • case1:
      pi<pj
      • 此時
        ai=aj
        矛盾 (以
        pi
        結尾的
        LIS
        勢必可以再接上
        pj
        ,使
        aj=ai+1
        )
    • case2:
      pi>pj
      • 此時
        bi=bj
        矛盾 (以
        pi
        結尾的
        LDS
        勢必可以再接上
        pj
        ,使
        bj=bi+1
        )
  • 故假設錯誤,代表

    len(LIS),len(LDS) 長度會比
    k
    大,代表
    k<n
    無解

構造

  • 構造方法如上面
    n=9,k=3
    的那個方法一樣
  • 分成
    n/k
    段,每一段從大排到小,兩段之間的開頭必須是從左到右遞增
  • 如果不夠讓每一段長度都是
    k
    ,那最後一段不用填滿沒關西
    • 例如
      n=8,k=3
    • [3,2,1,6,5,4,8,7]

CODE

#include <bits/stdc++.h> #define int long long #define pii pair<int, int> #define pb push_back #define mk make_pair #define F first #define S second #define ALL(x) x.begin(), x.end() using namespace std; using PQ = priority_queue<int, vector<int>, greater<int>>; const int INF = 2e18; const int maxn = 3e5 + 5; const int M = 1e9 + 7; int n, m, k; // 向下取整 int ifloor(int a, int b){ if(b < 0) a *= -1, b *= -1; if(a < 0) return (a - b + 1) / b; else return a / b; } // 向上取整 int iceil(int a, int b){ if(b < 0) a *= -1, b *= -1; if(a > 0) return (a + b - 1) / b; else return a / b; } void init() { cin >> n >> k; } // 開根號 int sqt (int x) { int tmp = sqrt (x); if (tmp * tmp < x) tmp++; return tmp; } void solve() { m = sqt (n); if (k < m) { cout << "IMPOSSIBLE\n"; return; } int num = iceil(n, k); // 分幾段 // 每一段有 k 個東西, 共 n/k 段 int pre = 0, start = k; for (int i = 0; i < num; i++) { // 3 2 1 6 5 4 9 8 7 for (int j = start; j > pre; j--) { cout << j << " "; } pre = start; start += k; if (start > n) start = n; // 太大變 n } cout << "\n"; } signed main() { // ios::sync_with_stdio(0); // cin.tie(0); int t = 1; cin >> t; while (t--) { init(); solve(); } }

參考資料

tags: 題解