题解概述
从查询位逆向跳跃,回溯至构造前的起始跳转位,即可推导出起始跳转位所对应的数字值,操作时间复杂度为 O ( q l o g ( n ) ) O(qlog(n)) O(qlog(n)).
读者不难达成以下共识
-
在构造过程中,序列的前半部分数字将会保持原位不动;
-
当有 n n n个数字时,初始序列共有 n − 1 n-1 n−1个空位,因此总共需要 n − 1 n-1 n−1次移动操作才能完成最终序列构造,即填满空位;
-
在构造过程中,第 i i i次操作的移动距离也为 i i i;
-
在构造过程中,一个独特数字的首次操作移动距离总是奇数;
-
一个独特数字的下一次移动距离会翻倍。
由上述共识可得,无需推演全部构造过程,即可用 O ( l o g n ) O(logn) O(logn)的复杂度推出某一个数字的最终位置。
利用上述共识可逆向迭代回溯出查询位所对应的起始数字值
首先易得,奇数查询位 的对应值为 ⌊ x 2 ⌋ + 1 \lfloor\frac{x}{2}\rfloor + 1 ⌊2x⌋+1 (见共识0) .
下面为 偶数查询位 的推导步骤:
-
查询位 x x x的左侧有 ⌊ x 2 ⌋ − 1 \lfloor\frac{x}{2}\rfloor - 1 ⌊2x⌋−1个初始填充空位;
-
由于共有 n − 1 n-1 n−1次移动操作,查询位 x x x末次移动为第 ( n − 1 ) − ( ⌊ x 2 ⌋ − 1 ) (n-1) - (\lfloor\frac{x}{2}\rfloor - 1) (n−1)−(⌊2x⌋−1)次移动,移动距离自然也是 ( n − 1 ) − ( ⌊ x 2 ⌋ − 1 ) (n-1) - (\lfloor\frac{x}{2}\rfloor - 1) (n−1)−(⌊2x⌋−1) (见共识2,第 i i i次操作的移动距离也为 i i i) ;
-
由共识3,4可得,每次移动距离的上一次移动距离会减半,直至移动距离为奇数(首次移动),设2中的末次移动距离为 l e n len len,我们可通过迭代 l e n / = 2 len /= 2 len/=2向右回推求解起始位,直至 l e n len len为奇数,设推出查询位 x x x的起始位置为 p o s pos pos;
-
构造前起始 p o s pos pos位的对应值为 ⌊ p o s 2 ⌋ + 1 \lfloor\frac{pos}{2}\rfloor + 1 ⌊2pos⌋+1.
逆向迭代递推代码如下(附正向构造过程代码):
/*
Backtracking code for construction
*/
#include <iostream>
using namespace std;
int main()
{
long long int n, q;
cin >> n >> q;
long long mid = (n+1) / 2;
for (int i = 0; i < q; i ++){
long long int x;
cin >> x;
if (x % 2 == 1){
cout << x / 2 + 1 << endl;
continue;
}
// (n - 1) == tot empty cells number, or tot moves
// (x/2 - 1) == pre empty cells number
long long int firstStepLength_reverse = (n-1) - (x/2 - 1);
long long int pos = x;
while (firstStepLength_reverse % 2 != 1){
pos += firstStepLength_reverse;
firstStepLength_reverse /= 2;
}
pos += firstStepLength_reverse;
cout << pos / 2 + 1 << endl;
}
return 0;
}
/*
Construction code
*/
/*
#include <iostream>
using namespace std;
int main()
{
long long int n, q;
cin >> n >> q;
long long int mid = (n+1) / 2;
for (int i = 0; i < q; i ++){
long long int x;
cin >> x;
if (x <= mid){
cout << x * 2 - 1 << endl;
continue;
}
long long int firstStepLength = (n-x+1) * 2 - 1;
long long int pos = x * 2 - 1;
while (pos > n){
pos -= firstStepLength;
firstStepLength *= 2;
}
cout << pos << endl;
}
return 0;
}
*/