【题解】CF1854A2 Dual (Hard Version)

发布时间 2023-09-08 15:48:28作者: ricky_lin

你考虑我们 A1 只需要通过自加凑一个最大的数,然后将所有的数都变成正数,最后做一次前缀和即可。(不懂可以看看落谷题解)

好,我们现在去看 Hard Version\(31\) 次操作怎么分配:

  • 前缀和(全为正)/ 后缀和 (全为负)—— \(19\)

  • 还剩下 \(12\) 次,不知道该怎么做。

我们的目标便变为了:在 \(12\) 次之内让所有数变成全为正/全为负

现在,我们需要整理一下我们手中的方法:

  • 我们 Easy Version 中是怎么做的呢,我们找了一个数,然后让它变成极大/极小,这一步在最劣情况下显然是 \(5\) 次 (\(1\to2\to4\to8\to16\to32\)),然后所有数都要变一遍 —— \(5+n\)
  • 当然,我们也有一种方法就是说找一个绝对值最大的数,然后让它给所有数加一遍,让其他的数和它符号相同 —— \(n\)

当然上面的 \(n\) 其实是上限,准确来说应该将 \(n\) 替换为和选出的数异号的数的个数。

那么,我们就可以将两种方法结合一下。

  • 我们先找到一个绝对值最大的数 \(x\),然后计算和这个数符号相同的数的个数(包括 \(0\)
  • 如果同号的数的个数 \(\geq 7\)(异号的数的个数 \(\leq 12\)),那么就直接让这个这些数都加上 \(x\)\(\leq 12~times\)
  • 否则,就造一个和 \(x\) 异号的绝对值极大的数,然后将 \(x\) 和与 \(x\) 同号的数都加上这个数。(\(\leq 5 + 6 + 1~times\)

最后将序列变为原序列的前/后缀和序列即可.

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 40;
int t,n;
int a[NN];
int maxn,pos;
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		maxn = 0,pos = 0;
		for(int i = 1; i <= n; ++i){
			scanf("%d",&a[i]);
			if(abs(a[i]) > maxn) maxn = abs(a[i]), pos = i;
		}
		if(maxn == 0){puts("0");continue;}
		int cnt = 0;
		for(int i = 1; i <= n; ++i){
			if(a[i] * a[pos] >= 0 && i != pos) ++cnt;
		}
		if(cnt == n-1){
			printf("%d\n",n-1);
		}
		else if(cnt < 7){
			printf("%d\n",5 + cnt + n);
			for(int i = 1; i <= n; ++i)
				if(a[i] * a[pos] < 0){pos = i;break;}
			for(int i = 1; i <= 5; ++i) printf("%d %d\n",pos,pos);
			for(int i = 1; i <= n; ++i)
				if(a[i] * a[pos] <= 0 && i != pos) printf("%d %d\n",i,pos);
		}
		else{
			printf("%d\n",n - cnt - 1 + n - 1);
			for(int i = 1; i <= n; ++i){
				if(a[i] * a[pos] < 0 && i != pos) printf("%d %d\n",i,pos);
			}
		}
		if(a[pos] > 0) for(int i = 1; i < n; ++i) printf("%d %d\n",i+1,i); 
		else for(int i = n; i > 1; --i) printf("%d %d\n",i-1,i);
	}
}