[CP / Codeforces] E. Vlad and an Odd Ordering(Div. 4)
我很喜欢这道题,第一是因为它对我来说很难,第二是因为它体现了数学中的一些美妙而神奇的性质。惭愧的是,初见时我并没能做出来,看了 editorial 后也似懂非懂,直到两个星期之后,才总算理清楚思路,并且给出了一种略微复杂的解法——不知道和官方题解是否重合。
题目大意是,你有标着 \(1\) 到 \(n\) 的 \(n\) 张卡片,现在你需要按如下顺序将它们依次摆放:
- 首先从小到大摆放所有标着奇数的卡片;
- 然后从小到大摆放所有标着奇数 \(\times{}2\) 的卡片;
- 然后从小到大摆放所有标着奇数 \(\times{}3\) 的卡片;
- ……
- 直到所有卡片都被摆放完毕。
问第 \(k\) 张被摆放的卡片上面标的数字是什么(为方便,以下省略 “卡片” 一词)。
观察数据规模,如果暴力模拟的话必然会超时,所以需要研究其背后的规律。
定义第 \(i\) 轮摆放的数字等于 “奇数 \(\times{}i\)”,为了找到突破口,我先模拟了一下 \(n=12\) 时的情况:
- 第 1 轮:\([1\ 3\ 5\ 7\ 9\ 11]\)
- 第 2 轮:\([2\ 6\ 10]\)
- 第 3 轮:\([]\)
- 第 4 轮:\([4\ 12]\)
- 第 5 轮:\([]\)
- 第 6 轮:\([]\)
- 第 7 轮:\([]\)
- 第 8 轮:\([8]\)
注意到只有 \(2^{i-1}\) 轮时才有数字被摆放,这是巧合吗?下面展开分析。
首先,第一轮摆放的必定是所有的奇数,这意味着除 \(1\) 以外的奇数轮(比如第 \(3\)、\(5\)、\(7\) 轮)都不会摆放任何数字,因为奇数和奇数的乘积依然是奇数,而所有的奇数已在第一轮被摆放完了。
观察第 \(6\) 轮,它是偶数轮,但它同样没有摆放任何数字,因为 “奇数 \(\times{}6\)” 中,\(6=2\times{}3\),所以在这一轮将要摆放的数字在第 \(2\) 轮中已经出现过了。
更一般地,我们会发现,所有满足 \(i=2^w\cdot{}m\)(\(m\) 为大于 \(1\) 的奇数,\(w\) 为正整数)的轮次都不会摆放任何数字,因为该轮次本应该摆放的数字早就在第 \(2^w\) 轮中出现过了。
因此得出结论,只有在轮次为 \(2^w\) 时才会摆放数字,所以我们可以统计每个这样的轮次摆放数字的总个数,逐个累加,直到找到 \(k\) 所处的轮次,且 \(k\) 在该轮次内 是第 \(j\) 个数,那么第 \(k\) 个被摆放的数字就等于 \(2^w\times{}(2j-1)\)。
每个轮次摆放的数字总个数可以通过以下方法获得:
- 计算 \(a=\lfloor{}\frac{n}{2^w}\rfloor{}\) 得到每一轮所乘的最大奇数
- 如果 \(a\) 是偶数,则将其减一
- 再通过 \(b=\lceil{}\frac{a}{2}\rceil{}\) 得到该轮所乘奇数的总个数,也即该轮摆放的数字个数
因此得到代码:
代码 1(\(O(logn)\))
void solve() {
int n, k;
std::cin >> n >> k;
int cnt = 0, w = 1, t;
while (1) {
t = n / w;
t -= !(t & 1);
t = (t + 1) / 2;
if (cnt + t >= k)
break;
cnt += t;
w <<= 1;
}
std::cout << ((k - cnt) * 2 - 1) * w << '\n';
}
官方题解提供了两种做法:第一种和我的解法类似,第二种采用了更为简洁的递归,但原理和前面其实是相同的,代码附在下面。
代码 2(\(O(logn)\))
int dfs(int n, int k) {
if (k <= (n + 1) / 2) {
return k * 2 - 1;
}
return 2 * dfs(n / 2, k - (n + 1) / 2);
}
void solve() {
int n, k, ans = 0;
std::cin >> n >> k;
std::cout << dfs(n, k) << '\n';
}