题目描述
对于一个排列,小红定义该排列的总消耗为:1走到2,2走到3,……,最终从 n − 1 n-1 n−1走到 n n n所需的最少的总步数。其中,每一步可以向左走一步,也可以向右走一步。
现在,小红只记得排列的大小 n n n和走的步数 k k k,但不记得排列的构造情况了。请你帮小红还原整个排列。
输入描述
两个正整数 n n n和 k k k,用空格隔开。
满足条件: 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105 和 n − 1 ≤ k ≤ n ∗ ( n − 1 ) / 2 n-1 \leq k \leq n*(n-1)/2 n−1≤k≤n∗(n−1)/2
输出描述
如果无解,请输出-1。
否则输出构造的排列。有多解时输出任意即可。
示例
示例1
输入:
3 2
输出:
1 2 3
说明:
小红从1号开始,向右跳两步即可先跳到2,再跳到3。输出[3,2,1]这个排列也符合要求。
示例2
输入:
4 6
输出:
1 3 4 2
思路
首先,定义一个双端队列dq
和一个位集vis
,用于记录每个位置的状态。然后,从输入中读取两个整数n
和k
,分别表示排列的大小和步数。
接着,计算j = k - (n - 1)
,这是一个关键步骤,其中n - 1
表示在一个递增排列中从1
走到n
的最小步数。然后,对于n - 2
到1
的每个i
,如果j
大于等于i
,就将j
减去i
,并将vis[i + 2]
设为1
,表示i+2
位置的数需要反向放置。
定义一个方向变量dir
,初始为0
,表示正向放置。然后,对于1
到n
的每个i
,如果vis[i]
为1
,就反转dir
。如果dir
为1
,就将i
从队列前端插入,否则从队列后端插入。
最后,遍历输出队列dq
中的每个元素,即为所求的排列。
输出描述中提到,如果无解,请输出-1。如果给定的步数 k 大于 n ∗ ( n − 1 ) / 2 n*(n-1)/2 n∗(n−1)/2 或小于 n − 1 n-1 n−1,那么就无法找到一个排列使得总消耗等于 k,即这个问题无解。但是输入描述中,给定 k 的范围为 n − 1 ≤ k ≤ n ∗ ( n − 1 ) / 2 n-1 \leq k \leq n*(n-1)/2 n−1≤k≤n∗(n−1)/2,所以无解的情况实际上是不会出现的,不需要特别考虑无解。
AC代码
#include <algorithm>
#include <bitset>
#include <iostream>
#include <deque>
#define AUTHOR "HEX9CF"
using namespace std;
using ll = long long;
const int N = 1e5 + 7;
ll n, k;
deque<int> dq;
bitset<N> vis;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
dq.clear();
vis.reset();
cin >> n >> k;
ll j = k - (n - 1);
for (int i = n - 2; i > 0; i--) {
if (j >= i) {
j -= i;
vis[i + 2] = 1;
}
}
bool dir = 0;
for (int i = 1; i <= n; i++) {
if (vis[i]) {
dir ^= 1;
}
if (dir) {
dq.push_front(i);
} else {
dq.push_back(i);
}
}
for (const auto i : dq) {
cout << i << " ";
}
cout << endl;
return 0;
}