Codeforces Round 889 (Div. 2) C1 - C2
Problem - C1 - Codeforces
Problem - C2 - Codeforces
题意:
有\(n\)个数字,可以选择任意两个位置\(i,j\)进行操作,使得\(a_i=a_i+a_j\)(也即是把\(a_j\)加到\(a_i\)上),输出任意操作方案使得数组最后是不降的。esay-version要求在50次操作内完成,hard-version要求在31次操作内完成。
hard-version和easy-version有共同之处,所以放在一起讲了。
思路:
首先,我们可以大致分成三种情况讨论:全为正数,全为负数,以及有正有负(在这里0不会对最终答案产生影响,可以放进任意一种情况里)
比较容易看出,对于全正的,我们只需要从左到右把\(a_{i}\)加到\(a_{i + 1}\)上,最后就一定能保证不降(每个数都等于它前一个数\(+k\),\(k\)大于等于\(0\),则每个数都大于等于其前一个数)
相对应的,对于全负的情况,我们只需要从右到左把\(a_i\)加到\(a_{i - 1}\)上,也可以保证不降。
可以容易看出,上面两种处理最多只需要操作19次,同时满足了两个版本的要求。
那么接着考虑有正有负。
对于easy-version,可以对数组中正数和负数的个数进行统计,同时记录正数中的最大值和负数中的最小值,如果正数个数大于负数个数,那么我们只需要让正数的最大值进行自加,直到大于负数最小值的绝对值,然后再把它加到所有负数上,就可以让所有负数变成正数,然后继续上面全正的操作即可。如果负数个数大于正数个数则同理。
上面方案中最坏的情况就是\(1,1,1,1,1,1,1,1,1,1,1,-20,-20,-20,-20,-20,-20,-20,-20,-20\),在这种情况下,\(1\)也只需自加\(5\)次就已经大于\(20\),加到每一个\(-20\)需要操作\(9\)次,进行全加操作\(19\)次,最终操作次数也只是\(5 + 9 + 19 = 33\),远远小于easy-version的操作上限,但却正好超过了hard-version的操作上限。
思考更好的方法,观察可以发现,对于上面的情况,如果我们直接把\(-20\)加到\(1\)上,使数组变成全负,那么总共只需要\(11 + 19=30\)次操作,就可以达成要求,对于hard-version的上限\(31\),最多可以将大数加到别的数上\(12\)次(若数组大小为20),则总结一下,如果正数最大值大于等于负数最小值的绝对值,且负数个数小于等于\(12\),那么我们执行直接变成全正相加会是更优的,若负数最小值的绝对值大于等于正数最大值则同理。如果都不满足,再使用一开始的思路(自增)就可以了。
事实上上面的极限个数还会随着数组大小变化,但这个方法只会更优,且一定满足条件,所以不用继续优化也可以。
void QAQ(){
int n;
cin >> n;
int minn = INF, maxx = -INF;
vector<int> num(n + 1);
for(int i = 1; i <= n; i ++){
cin >> num[i];
maxx = max(maxx, num[i]);
minn = min(minn, num[i]);
}
queue<pii> op;
if(minn >= 0){//全正
for(int i = 1; i < n; i ++){
// cout << i + 1 << " " << i << endl;
op.push({i + 1, i});
}
}
else if(maxx <= 0){//全负
for(int i = n; i > 1; i --){
// cout << i - 1 << " " << i << endl;
op.push({i - 1, i});
}
}
else{//有正有负另外统计
int a = 0, b = 0, aa = 0, bb = 0;
int acnt = 0, bcnt = 0;
for(int i = 1; i <= n; i ++){
if(num[i] > 0){
if(num[i] > a){
aa = i, a = num[i];
}
acnt ++;
}
else if(num[i] < 0){
if(num[i] < b){
bb = i, b = num[i];
}
bcnt ++;
}
}
//如果是easy-version直接看最后的else也可以
if(abs(a) > abs(b) && bcnt <= 12){
for(int i = 1; i <= n; i ++){
if(num[i] < 0){
op.push({i, aa});
}
}
for(int i = 1; i < n; i ++){
op.push({i + 1, i});
}
}
else if(abs(b) > abs(a) && acnt <= 12){
for(int i = 1; i <= n; i ++){
if(num[i] > 0){
op.push({i, bb});
}
}
for(int i = n; i > 1; i --){
// cout << i - 1 << " " << i << endl;
op.push({i - 1, i});
}
}
else{
if(acnt > bcnt){
//正数多
while(abs(a) < abs(b)){
a += a;
op.push({aa, aa});
}
for(int i = 1; i <= n; i ++){
if(num[i] < 0){
op.push({i, aa});
}
}
for(int i = 1; i < n; i ++){
op.push({i + 1, i});
}
}
else{
while(abs(b) < abs(a)){
b += b;
op.push({bb, bb});
}
for(int i = 1; i <= n; i ++){
if(num[i] > 0){
op.push({i, bb});
}
}
for(int i = n; i > 1; i --){
// cout << i - 1 << " " << i << endl;
op.push({i - 1, i});
}
}
}
}
cout << op.size() << endl;
while(!op.empty()){
int x, y;
tie(x, y) = op.front();
op.pop();
cout << x << " " << y << endl;
}
}