我发现对于向下取整向上取整的题目(特指除二),没有一些常见的思路积累。
Description
给定一个长度为 \(n\) 的序列 \(\{a_n\}\)。每次操作你需要选择一个整数 \(x\) 并将所有 \(a_i\) 替换为 \(\lfloor \frac {a_i + x}2 \rfloor\)。求至少多少次操作后能将所有 \(a_i\) 变相同。
若最少次数小于等于 \(n\),输出操作次数和每次操作所选择的 \(x\)。否则仅输出操作次数。
\(1 \le n \le 2 \times 10^5\),\(0 \le a_i \le 10^9\)。
Solution
可以观察到,每次操作后,数字的相对大小关系不会发生改变。
例如 \(a = \{4, 2, 5\}\),\(x = 3\),那么操作后的 \(a' = \{3, 2, 4\}\)。可以发现 \(a_3\) 和 \(a'_3\) 分别都是两个序列的最大值,\(a_2\) 和 \(a'_2\) 都是两个序列的最小值。
那么就启发我们不用同时维护原序列中的所有值,只需要维护序列中的最大值和最小值。当这两个值相等时,就代表序列中所有的数都相同了。
那么问题就被转化成了这样:
给定两个整数 \(a, b(a > b)\)。每次操作你需要选择一个整数 \(x\) 并将 \(a \gets \lfloor \frac {a + x}2 \rfloor\),\(b \gets \lfloor \frac {b + x}2 \rfloor\)。求至少多少次操作后 \(a = b\)。
每次操作我们肯定是希望利用下取整这个优秀性质来让 \(a\) 尽量接近 \(b\),也就是想让操作后 \(a - b\) 尽量小。那么我们可以直接分讨:
- \(a\) 为偶数,\(b\) 为偶数:\(x\) 任意取值;
- \(a\) 为奇数,\(b\) 为奇数:\(x\) 任意取值;
- \(a\) 为偶数,\(b\) 为奇数:\(x\) 任意一个奇数;
- \(a\) 为奇数,\(b\) 为偶数:\(x\) 任意一个偶数。
然后就解决了。用 vector
记录答案即可。
Code
void solve()
{
cin >> n;
a = 0, b = 2e9;
for (int i = 1; i <= n; ++ i )
{
cin >> x;
a = max(a, x);
b = min(b, x);
}
vector<int> res;
while (a != b)
{
if (a % 2 == 0 && b % 2 == 0) x = rand();
else if (a % 2 == 1 && b % 2 == 1) x = rand();
else if (a % 2 == 0 && b % 2 == 1) x = rand() * 2 + 1;
else x = rand() * 2;
a = (a + x) / 2;
b = (b + x) / 2;
res.push_back(x);
}
cout << res.size() << '\n';
if (res.size() <= n)
{
for (auto i : res) cout << i << ' ';
if (res.size()) puts("");
}
return;
}
题解的四个超链接很重要,对于除以二的向下取整的值,可以直接把奇数的转成减一除二。
\(a\) 为偶数,\(b\) 为偶数
令 \(\Delta\) 为操作后 \(a\) 与 \(b\) 的差。
- 若选择的 \(x\) 为偶数: \(a' = \dfrac {a + x} 2\),\(b' = \dfrac {b + x}2\),\(\Delta = \dfrac {a - b} 2\)。 -
- 若选择的 \(x\) 为奇数: \(a' = \dfrac {a + x - 1} 2\),\(b' = \dfrac {b + x - 1} 2\),\(\Delta = \dfrac {a - b}2\)。
- 可以发现,无论 \(x\) 怎样取值,两数差一定为 \(\dfrac {a - b}2\)。因此 \(x\) 任意取值。