---
# System prepended metadata

title: '[CSES 2215](https://cses.fi/problemset/task/2215/)'
tags: [題解]

---

{%hackmd @ioncamp/__style %}
# [CSES 2215](https://cses.fi/problemset/task/2215/)

## 題目

- 構造一個 $1..n$ 的排列使得他的 **最長單調子序列** 恰好長度為 $k$

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

## 想法

> $\texttt{LIS}$ 為 $\texttt{longest increasing subsequnce}$ 
>
> $\texttt{LDS}$ 為 $\texttt{longest decreasing subsequnce}$ 

### 觀察

- $\texttt {ans} = \texttt{max} \begin{cases} \texttt{len(LIS)} \\ \texttt{len(LDS)}\end{cases}$

- 發現說要想使答案控制得很好需要使得 $\texttt{len(LIS)}$ 跟 $\texttt{len(LDS)}$ 取得平衡

- 例如 $n=9,k=3$ 那我必須使得 $\texttt{len(LIS) = len(LDS) = 3}$ 

  - 使得 $\texttt{LIS}$ 和 $\texttt{LDS}$ 不互相影響

  - $[\underline{\color{red}3 \color{black},2,1},\underline{\color{red}6 \color{black},5,4},\underline{\color{red}9 \color{black},8,7}]$

  - 紅色為其中一個 $\texttt{LIS}$ 底線為 $\texttt{LDS}$

### 證明

> $\texttt{proof:}$
>
> $k < \sqrt{n}$ 無解

- 假設存在一個長度 $n=k^2+1$ 的排列 $p$，它的 $\texttt{LIS}$ 與 $\texttt{LDS}$ 長度皆最多為 $k$
  - 此時 $k<\sqrt{n}$
- $a_i=$ 以 $a_i$ 結尾的 $\texttt{LIS}$ 長度
- $b_i=$ 以 $b_i$ 結尾的 $\texttt{LDS}$ 長度
- 對於所有 $i$ ， $1 \le a_i,b_i\le k$ (我們的假設)
- 因為 $(a_i,b_i)$ 有 $k^2$ 種可能 (籠子)
- 但目前 $i=1..k^2+1$ 有 $k^2+1$ 種 (鴿子)
- 可由鴿籠原理得知必存在兩數 $i,j$ 使 $a_i=a_j,b_i=b_j,(i<j)$

> 鴿籠原理
>
> - 若有 $n$ 個籠子和 $n+1$ 隻鴿子，所有的鴿子都被關在鴿籠裡，那麼至少有一個籠子有至少 $2$ 隻鴿子。

- 那接著分以下 $\texttt{case:}$ 
  - $\texttt{case1:}$ $p_i<p_j$
    - 此時 $a_i=a_j$ 矛盾 (以 $p_i$  結尾的 $\texttt{LIS}$勢必可以再接上 $p_j$，使 $a_j=a_i+1$ )
  - $\texttt{case2:}$ $p_i>p_j$
    - 此時 $b_i=b_j$ 矛盾 (以  $p_i$  結尾的 $\texttt{LDS}$ 勢必可以再接上 $p_j$，使 $b_j=b_i+1$ ) 

- 故假設錯誤，代表 $\texttt{len(LIS),len(LDS)}$ 長度會比 $k$ 大，代表 $k<\sqrt{n}$ 無解

### 構造

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

## CODE

```cpp=
#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();
    }
} 
```

## 參考資料
- https://abc864197532.github.io/2021/09/15/cses-additional-sol/

###### tags: `題解`