Dual(构造)

发布时间 2023-08-15 23:55:42作者: o-Sakurajimamai-o

题目描述

Popskyy & tiasu - Dual

[Popskyy & tiasu - Dual](https://soundcloud.com/popskyy/popskyy-tiasu-dual)

The only difference between the two versions of this problem is the constraint on the maximum number of operations. You can make hacks only if all versions of the problem are solved.

You are given an array $ a_1, a_2,\dots, a_n $ of integers (positive, negative or $ 0 $ ). You can perform multiple operations on the array (possibly $ 0 $ operations).

In one operation, you choose $ i, j $ ( $ 1 \leq i, j \leq n $ , they can be equal) and set $ a_i := a_i + a_j $ (i.e., add $ a_j $ to $ a_i $ ).

Make the array non-decreasing (i.e., $ a_i \leq a_{i+1} $ for $ 1 \leq i \leq n-1 $ ) in at most $ 31 $ operations. You do not need to minimize the number of operations.

输入格式

Each test contains multiple test cases. The first line contains the number of test cases $ t $ ( $ 1 \le t \le 500 $ ). The description of the test cases follows.

The first line contains a single integer $ n $ ( $ 1 \le n \le 20 $ ) — the length of the array.

The second line contains $ n $ integers $ a_1, a_2, \ldots, a_n $ ( $ -20 \le a_i \le 20 $ ) — the array before performing the operations.

输出格式

For each test case, output your operations in the following format.

The first line should contain an integer $ k $ ( $ 0 \le k \le 31 $ ) — the number of operations.

The next $ k $ lines represent the $ k $ operations in order. Each of these $ k $ lines should contain two integers $ i $ and $ j $ ( $ 1 \leq i, j \leq n $ ) — the corresponding operation consists in adding $ a_j $ to $ a_i $ .

After all the operations, the array $ a_1, a_2,\dots, a_n $ must be non-decreasing.

提示

In the first test case, by adding $ a_1 = 2 $ to $ a_2 $ , we get the array $ [2, 3] $ which is non-decreasing.

In the second test case, the array changes as:

- $ [1, 2, -10, 3] $
- $ [1, 2, -10, 6] $
- $ [1, 2, -10, 12] $
- $ [1, 2, 2, 12] $

In the third test case, the final array is $ [2, 3, 3, 3, 3] $ .

输入输出样例

输入 #1
10
2
2 1
4
1 2 -10 3
5
2 1 1 1 1
8
0 0 0 0 0 0 0 0
5
1 2 -4 3 -10
10
11 12 13 14 15 -15 -16 -17 -18 -19
7
1 9 3 -4 -3 -2 -1
3
10 9 8
20
1 -14 2 -10 6 -5 10 -13 10 7 -14 19 -5 19 1 18 -16 -7 12 8
20
-15 -17 -13 8 14 -13 10 -4 11 -4 -16 -6 15 -4 -2 7 -9 5 -5 17
输出 #1
1
2 1
3
4 4
4 4
3 4
4
2 1
3 1
4 1
5 1
0
7
3 4
3 4
5 4
5 4
5 4
5 4
5 4
15
6 1
6 1
6 1
7 2
7 2
7 2
8 3
8 3
8 3
9 4
9 4
9 4
10 5
10 5
10 5
8
3 4
3 4
2 4
2 4
2 4
2 4
1 4
1 4
3
2 1
3 1
3 1
31
14 1
18 7
13 11
15 11
6 4
5 17
19 6
19 12
10 5
11 12
1 17
15 19
16 10
14 2
16 11
20 7
7 6
9 5
3 6
6 14
17 18
18 14
12 3
17 16
8 18
13 16
9 8
14 8
16 2
11 8
12 7
31
5 12
19 13
9 1
5 17
18 19
6 16
15 8
6 9
15 14
7 10
19 7
17 20
14 4
15 20
4 3
1 8
16 12
16 15
5 6
12 10
11 15
20 3
20 19
13 14
11 14
18 10
7 3
12 17
4 7
13 2
11 13

说明/提示

In the first test case, by adding 1=2a1=2 to 2a2 , we get the array [2,3][2,3] which is non-decreasing.

In the second test case, the array changes as:

  • [1,2,−10,3][1,2,10,3]
  • [1,2,−10,6][1,2,10,6]
  • [1,2,−10,12][1,2,10,12]
  • [1,2,2,12][1,2,2,12]

In the third test case, the final array is [2,3,3,3,3][2,3,3,3,3] .

 

贪心地去想,我们一定尽可能让每次走的次数都具有价值。现在这样想,如果数组之内的正数多,那么我们让一个最大的数自增,增到什么程度呢?直到我们能把数组内的所有数变成正数为止。相反,如果负数多,那么我们把所有的数变为负数。显而易见,有一个规律:如果全部是正数,那么我们只需要用后一个数加上前一个数就可以了。

由于次数限制是 $31$ 次,我们每次相加需要消耗 $19$ 次。那么留给我们全部置负或者正的次数为 $12$ 次。如果正数多,就按正数来;如果负数多,就按负数来。这里注意,如果此时数组里只有 $1$ 和 $-20$,那么我们的 $1$ 需要自增 $5$ 次才够。显然不如直接用 $-20$ 来得快,可以省去这 $5$ 次。相同地,如果我们的正负数之差小于 $5$,可以按照其相反的次序来。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
using namespace std;

const int N=1e6+10,mod=1e9+7;  // 定义常量 N 和 mod,用于数组大小和取模运算
string s;  // 未使用,字符串变量 s
int n,t,a[N],res,num,ans,m,k_z,k_f;  // 定义各种整数变量和数组
int z,f,f_num,z_num,f_sum,z_sum;  // 定义整数变量,用于记录正数和负数的数量及和
int maxx,minn,max_id,min_id;  // 定义整数变量和索引,用于记录数组的最大和最小值的位置
int cnt1[N],cnt2[N];  // 计数数组,用于记录操作次数
map<int,pair<int,int>>mp1,mp2;  // map 容器,用于记录操作的位置

bool vis[N];  // 布尔数组,未使用

// 处理正数情况的函数
void solve_z()
{
        minn=abs(minn);
        // 通过操作,将最小负数变为正数的绝对值
        while(a[max_id]<minn) res++,a[max_id]+=a[max_id],z_num++;
        res+=(n-1);
        // 将其他负数变为正数
        for(int i=1;i<=n;i++) if(a[i]<0) res++,cnt1[i]++,mp1[i]={i,max_id},a[i]+=a[max_id];
        cout<<res<<endl;
        // 输出操作次数和具体操作的位置
        while(z_num!=0) z_num--,cout<<k_z<<" "<<k_z<<endl;
        for(int i=1;i<=n;i++) while(cnt1[i]!=0) cnt1[i]--,cout<<mp1[i].first<<" "<<mp1[i].second<<endl;
        for(int i=1;i<=n-1;i++) cout<<i+1<<" "<<i<<endl;
}

// 处理负数情况的函数
void solve_f()
{
        while((-a[min_id])<maxx) a[min_id]+=a[min_id],res++,f_num++;
        res+=(n-1);
        // 将其他正数变为负数
        for(int i=1;i<=n;i++) if(a[i]>0) res++,cnt1[i]++,mp1[i]={i,min_id},a[i]+=a[min_id];
        cout<<res<<endl;
        // 输出操作次数和具体操作的位置
        while(f_num!=0) f_num--,cout<<k_f<<" "<<k_f<<endl;
        for(int i=1;i<=n;i++) while(cnt1[i]!=0) cnt1[i]--,cout<<mp1[i].first<<" "<<mp1[i].second<<endl;
        for(int i=n;i>=2;i--) cout<<i-1<<" "<<i<<endl;
}

// 主处理函数
void solve()
{
    cin>>n;  // 输入数组长度
    z=f=num=res=ans=m=z_num=f_num=z_sum=f_sum=0;
    mp1.clear();  // 清空 map 容器
    memset(cnt2,0,sizeof cnt2);  // 初始化计数数组
    a[0]=-30,maxx=-30,minn=30;  // 初始化数组和最大最小值
    for(int i=1;i<=n;i++){
        cin>>a[i];  // 输入数组元素
        if(a[i]>0) z++,z_sum+=a[i];  // 统计正数个数和和
        else if(a[i]==0) ;  // 不处理为零的情况
        else f++,f_sum+=a[i];  // 统计负数个数和和
        if(maxx<a[i]) maxx=a[i],max_id=i,k_z=i;  // 更新最大值和位置
        if(minn>a[i]) minn=a[i],min_id=i,k_f=i;  // 更新最小值和位置
    }
    // 根据正数和负数的数量及和进行处理
    if(z>f)
        if(z_sum<abs(f_sum)&&abs(z-f)<=5)
            solve_f();
        else
            solve_z();
    else{
        if(z_sum>abs(f_sum)&&abs(z-f)<=5)
            solve_z();
        else
            solve_f();
    }
}

signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;  // 输入测试用例数量
    while(t--){
        solve();  // 处理每个测试用例
    }
    return 0;
}