CF1080D Olya和神奇的正方形
1 CF1080D Olya和神奇的正方形
2 题目描述
最近,\(Olya\) 收到了一个大小为 \(2^n*2^n\) 神奇的正方形。
在她妹妹看来,一个正方形很无聊。因此,她要求 \(Olya\) 执行精确的 \(k\) 分裂操作。
分裂操作就是 \(Olya\) 把一个边长为 \(a\) 的正方形切割成4个边长为 \(\frac{a}{2}\) 相等的正方形。 如果正方形的边长等于 \(1\),那么就不能再对其进行分裂操作。
\(Olya\) 很乐意满足姐姐的要求,但她也希望在操作完成后自己也能开心。
如果能满足下面的条件,\(Olya\) 也会很开心。设左下方的边长等于 \(a\),则右上方的边长也应等于 \(a\)。它们之间也应该有一条路径,它只由边长为 \(a\) 的正方形组成。路径上的所有连续正方形都应该有一个共同的侧面。显然,只要我们有一个正方形,这些条件就满足了。因此,\(Olya\) 准备在自己开心的基础上满足她妹妹的要求。请告诉 \(Olya\):是否可以按照一定的顺序执行 \(k\) 分裂操作,使 \(Olya\) 开心?如果可能的话,给出所有从左下方到右上方的路径包含的正方形侧面的大小。
3 题解
我们发现:一个边长为 \(2^i\) 的正方形最多可以拆分的次数是边长为 \(2^{i-1}\) 的正方形的拆分次数的 \(4\) 倍 \(+1\) 次,将这个式子设为 \(f\),则有 \(f(x) = f(x-1) * 4 + 1\)。由于这个式子的值在第 \(31\) 项的时候超过了 \(10^{18}\),所以在 \(n > 31\) 的时候我们可以直接输出 \(YES\) 和 \(n-1\)。
当 \(n \le 31\) 时,我们按照经过的正方形的边长的对数枚举。对于经过边长为 \(2^i\) 的情况,我们最多可以进行的拆分数是 \(f(n) - (2*2^{n-i} - 1) * f(i)\)。意思就是留出一条供我们走到右上角的由边长为 \(2^i\) 的正方形组成的路,剩下的正方形能拆就拆。而最小值是仅在边缘拆出一条供我们走的路。我们发现:我们在拆出边长为 \(2^i\) 的沿着边缘的一些正方形后,如果要再拆分出边长为 \(2^{i-1}\) 的正方形,需要的次数为上一次的次数 \(*2-1\)。这是因为上一次拆分后每个拆分都产生了沿着边缘的两个更小的正方形,而 \(-1\) 是因为在拐角处的两个正方形合成了一个。那么我们就得出了答案:最少可以进行的拆分数是 \(\sum_{j=1}^{n-i}(2^j - 1)\)。
对于每一个 \(n\) 和 \(k\),我们只需要计算出其最多可以进行的拆分数和最少可以进行的拆分数,然后查看 \(k\) 是否位于其中间即可。
4 代码(空格警告):
#include <iostream>
using namespace std;
#define int long long
int T, n, k, x, l, r, flag;
int sum1[40], sum2[40];
signed main()
{
x = 1;
for (int i = 1; i <= 31; i++)
{
x *= 2;
sum1[i] = sum1[i-1] + x - 1;
}
for (int i = 1; i <= 31; i++) sum2[i] = sum2[i-1] * 4 + 1;
cin >> T;
while (T--)
{
flag = 0;
cin >> n >> k;
if (n > 31) cout << "YES " << n-1 << endl;
else
{
for (int i = 0; i < n; i++)
{
l = sum1[n-i];
r = sum2[n] - ((1 << (n - i + 1)) - 1) * sum2[i];
if (k >= l && k <= r)
{
cout << "YES " << i << endl;
flag = 1;
break;
}
}
if (!flag) cout << "NO" << endl;
}
}
return 0;
}