题目描述
难度分:1400
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和长为n的数组a(0≤a[i]≤109)。
每次操作,你可以选一个在[0,1018]中的整数x,然后把每个a[i]都更新成 ⌊a[i]+x2⌋。至少操作多少次,可以使所有a[i]都相同?
输出最小操作次数。如果操作次数≤n,额外输出每次操作选的x。
输入样例
4
1
10
2
4 6
6
2 1 2 1 2 1
2
0 32
输出样例
0
2
2 5
1
1
6
算法
打表找规律
看到x∈[0,1018],且第二个case答案是2 5
就感觉出题人满满的恶意,让人觉得x的取值可以千奇百怪。实际上x=0或者1就可以,本质上就是每个数是向下取整还是向上取整。
我们可以造一些小case,打个表观察一下,发现当最小值是奇数的时候x=1,偶数的时候x=0。考虑到这个floor操作并不会影响数组中元素的相对大小,这样就可以模拟了,不断对最大值和最小值操作,直到最大值和最小值相等,把中间的x保存到一个答案数组中。
如果答案数组长度≤n就输出长度和各个元素,否则就只输出长度。
复杂度分析
时间复杂度
整个算法就是整个数组的最大值和最小值不断除以2(可能向上取整也可能向下取整),找到最值的时间复杂度是O(n),模拟的时间复杂度是O(log2A),其中A是数组中元素的值域。
空间复杂度
空间消耗的瓶颈在于答案数组,需要存O(log2A)级别的元素个数,因此额外空间复杂度为O(log2A)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
int t, n;
LL a[N];
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
LL mn = 1e9, mx = -1;
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
mn = min(mn, a[i]);
mx = max(mx, a[i]);
}
if(n == 1) {
puts("0");
}else {
vector<int> ans;
while(mn != mx) {
int x = mn&1;
ans.push_back(x);
mn = (mn + x) >> 1;
mx = (mx + x) >> 1;
}
printf("%d\n", (int)ans.size());
if(ans.size() <= n) {
for(int x: ans) {
printf("%lld ", x);
}
}
puts("");
}
}
return 0;
}