2024-2025 ICPC, NERC, Southern and Volga Russian Regional Contest B. Make It Equal
因为和题解有所区别,所以写一发题解增长见识。
题面
给你一个大小为 \(n\) 的整数数组 \(a\) 。数组元素的编号从 \(1\) 到 \(n\) 。
您可以执行以下任意次数的操作(可能为 0 次):从 \(1\) 到 \(n\) 中选择一个索引 \(i\) ;将 \(a_i\) 减少 \(2\) ,并将 \(a_{(i \bmod n) + 1}\) 增加 \(1\) 。
执行这些操作后,数组中的所有元素都应是非负等整数。
你的任务是计算最少需要执行的操作次数。
题解
考虑将 \(a_i\) 写作二进制形式,从第 \(0\) 位开始计数(方便描述)。
如果第 \(j(j > 0)\) 位为 \(1\),\(a_i\) 可以自减 \(2^j\),操作次数为 \(2^{j-1}\),同时对 \(1 \le t < j\) 的每个 \(a_{i + t}\) 做 \(2^{j - t - 1}\) 次操作,可以发现,只有 \(a_{i+j}\) 的值增加了 \(1\)。
循环操作之后,可以发现所有的 \(a_i\) 是小于等于 \(1\) 的,如果所有的 \(a_i\) 不都等于 \(1\),那么答案不存在,考虑退回一次操作需要让 \(a_i \ge 2\),让所有的 \(a_i \ge 1\) 后容易发现对所有位置做一次操作后回到了 \(a_i \le 1\),且恰好为原来位置的取反值,因此只有全 \(0\) 和全 \(1\) 有解。
对于最小的解只需要所有操作数减去最小操作数即可。
第一次循环操作复杂度是 \(O(n\log^2{a_i})\),第二次循环操作是 \(O(n)\) 的。
对赋值的操作精细化可以去掉第一次循环的 \(O(\log{a_i})\),用 \(\operatorname{lowbit}\) 优化直接去计算需要减去的值可以去掉对位的枚举做到 \(O(n)\)。
时间复杂度 \(O(n\log^2{a_i})\) 的代码如下,\(O(n)\) 应该不难实现。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve()
{
int n;
cin >> n;
vector<ll> a(n), cnt(n);
for (int i = 0; i < n; i ++ ) cin >> a[i];
int pd = 0;
while (!pd)
{
pd = 1;
for (int i = 0; i < n; i ++ )
{
if (a[i] <= 1) continue;
pd = 0;
int t = 1;
while (1ll << (t + 1) <= a[i]) t ++ ;
for (int j = t; j; j -- )
{
if (a[i] >> j & 1)
{
a[i] ^= 1ll << j;
a[(i + j) % n] ++ ;
for (int k = 0; k < j; k ++ ) cnt[(i + k) % n] += 1ll << (j - k - 1);
}
}
}
}
int num = count(a.begin(), a.end(), 1);
if (num != n) cout << "-1\n";
else
{
ll minn = *min_element(cnt.begin(), cnt.end());
ll res = accumulate(cnt.begin(), cnt.end(), 0) - n * minn;
cout << res << "\n";
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T -- ) solve();
return 0;
}