E. Binary Inversions

E. Binary Inversions

You are given a binary array of length n. You are allowed to perform one operation on it at most once. In an operation, you can choose any element and flip it: turn a 0 into a 1 or vice-versa.

What is the maximum number of inversions the array can have after performing at most one operation?

A binary array is an array that contains only zeroes and ones.

The number of inversions in an array is the number of pairs of indices i,j such that i<j and ai>aj.

Input

The input consists of multiple test cases. The first line contains an integer t (1t104) — the number of test cases. The description of the test cases follows.

The first line of each test case contains an integer n (1n2105) — the length of the array.

The following line contains n space-separated positive integers a1,a2,,an (0ai1) — the elements of the array.

It is guaranteed that the sum of n over all test cases does not exceed 2105.

Output

For each test case, output a single integer — the maximum number of inversions the array can have after performing at most one operation.

Example

input

复制代码
5
4
1 0 1 0
6
0 1 0 0 1 0
2
0 0
8
1 0 1 1 0 0 0 1
3
1 1 1
复制代码

output

3
7
1
13
2

Note

For the first test case, the inversions are initially formed by the pairs of indices (1,2), (1,4), (3,4), being a total of 3, which already is the maximum possible.

For the second test case, the inversions are initially formed by the pairs of indices (2,3), (2,4), (2,6), (5,6), being a total of four. But, by flipping the first element, the array becomes 1,1,0,0,1,0, which has the inversions formed by the pairs of indices (1,3), (1,4), (1,6), (2,3), (2,4), (2,6), (5,6) which total to 7 inversions which is the maximum possible.

 

解题思路

  注意到这题本质就是问在最多置换一位的情况下,序列的最大逆序对个数是多少。

前缀后缀和

  先给出最容易想到的做法。

  这种做法是对暴力枚举的优化,把求改变后的序列的逆序对个数优化到O(1)。维护序列前缀中 1 的个数和维护序列后缀中 0 的个数,同时假设原序列的逆序对个数为sum,然后枚举每一位,

  1. 如果ai=0,那么要把ai置换成1,此时改变后的序列的逆序对个数应该是,原来总的逆序对个数sum减去第i个位置的逆序对个数(即第i个位置之前1的个数,也就是prei1),由于ai变成了1因此第i个位置之后所有是0的位置的逆序对个数都增加了1个,所以还要加上sufi+1sufi+1表示第i个位置之后0的个数)。所以变化后的序列的逆序对个数就是sumprei1+sufi+1
  2. 如果ai=1,那么要把ai置换成0,此时改变后的序列的逆序对个数应该是,原来总的逆序对个数sum加上第i个位置的逆序对个数,由于ai变成了0因此第i个位置之后所有是0的位置的逆序对个数都减少了1个,所以还要减去sufi+1。所以变化后的序列的逆序对个数就是sum+prei1sufi+1

  AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 2e5 + 10;
 7 
 8 int a[N], pre[N], suf[N];
 9 
10 void solve() {
11     int n;
12     scanf("%d", &n);
13     LL sum = 0;    // 原序列总的逆序对个数
14     pre[0] = 0, suf[n + 1] = 0;
15     for (int i = 1; i <= n; i++) {
16         scanf("%d", a + i);
17         pre[i] = pre[i - 1] + a[i];    // 前缀和,pre[i]表示包括i前面1的个数
18         if (a[i] == 0) sum += pre[i];
19     }
20     for (int i = n; i; i--) {
21         suf[i] = suf[i + 1] + !a[i];    // 后缀和,suf[i]表示包括i后面0的个数
22     }
23     LL ret = sum;
24     for (int i = 1; i <= n; i++) {
25         // 如果a[i] = 1,则置换变成0,sum先加上i这个位置的逆序对个数,即加上pre[i-1],
26         // 由于a[i]变成0,i+1后面所有是0的位置逆序对个数都要减少一个,一共减少了suf[i+1]个
27         if (a[i]) ret = max(ret, sum + pre[i - 1] - suf[i + 1]);
28         // 如果a[i] = 0,则置换变成1,sum先减去i这个位置的逆序对个数,即减去pre[i-1],
29         // 由于a[i]变成1,i+1后面所有是0的位置逆序对个数都要增加一个,一共增加了suf[i+1]个
30         else ret = max(ret, sum - pre[i - 1] + suf[i + 1]);
31     }
32     printf("%lld\n", ret);
33 }
34 
35 int main() {
36     int t;
37     scanf("%d", &t);
38     while (t--) {
39         solve();
40     }
41     
42     return 0;
43 }
复制代码

 

贪心

  比赛的时候虽然是猜出来的做法,但我感觉并不直观,官方题解也没给出这种做法的正确性证明,因此来补充一下较为严格的证明。  

  先说结论,答案就是对下面三种情况取最大值:

  1. 统计0次操作时(即原序列)逆序对的个数。
  2. 从左往右找到第一次出现的0,置换成1,再求逆序对的个数。
  3. 从右往左找到第一次出现的1,置换成0,再求逆序对的个数。

  其实是先猜做法再证明正确性的。容易想到最暴力的做法是枚举每一位并置换,然后对新的序列求逆序对。这种做法的时间复杂度是O(n2),会超时。

  无非就是把每一个0变成1求一次逆序对,或者是把每一个1变成0求一次,因此如果要优化的话容易想到是否存在某个位置的0,在置换这个位置的0后得到的逆序对个数比置换其他位置的0要多(同理1)。

  假设序列中0的个数为x,并假设s[k] (1kx) ,表示序列中第k0左边1的个数(理解为这个位置的逆序对个数),容易知道s[k]是个递增序列。

  当不执行操作时,整个序列的逆序对个数就是k=1xs[k]。现在置换第i个位置的0 (1ix),那么逆序对个数就变成k=1xs[k]+(xi)s[i]。注意到k=1xs[k]+x是定值,而(i+s[i])是变化的量,由于随着i的递增,i+s[i]会递增,因此(i+s[i])会递减,因此k=1xs[k]+(xi)s[i]会递减,因此要使得逆序对最大,那么i应该取最小值,即应该置换第一个0的位置。

  同理证明1置换成0的情况,我们只需注意到求逆序对还可以根据1的位置往右统计0的个数(上面的做法是根据0的位置统计左边1的个数,这两种统计方法的等价的)。那么我们reverse整个序列,此时就变成了根据1的位置统计左边0的个数,根据上面的证明可以知道应该置换左边第一个1的位置能够得到最大逆序对,注意由于这是对于reverse后的序列的说法,因此对于原序列应该是置换最后一个1的位置。

  最后对这三种情况取最大值就可以了。

  AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 2e5 + 10;
 7 
 8 int n;
 9 int a[N];
10 
11 LL get() {
12     LL ret = 0;
13     for (int i = 1, s = 0; i <= n; i++) {
14         if (a[i] == 0) ret += s;
15         s += a[i];
16     }
17     return ret;
18 }
19 
20 void solve() {
21     scanf("%d", &n);
22     for (int i = 1; i <= n; i++) {
23         scanf("%d", a + i);
24     }
25     LL ret = get();
26     for (int i = 1; i <= n; i++) {
27         if (a[i] == 0) {
28             a[i] = 1;
29             ret = max(ret, get());
30             a[i] = 0;
31             break;
32         }
33     }
34     for (int i = n; i; i--) {
35         if (a[i] == 1) {
36             a[i] = 0;
37             ret = max(ret, get());
38             a[i] = 1;
39             break;
40         }
41     }
42     printf("%lld\n", ret);
43 }
44 
45 int main() {
46     int t;
47     scanf("%d", &t);
48     while (t--) {
49         solve();
50     }
51     
52     return 0;
53 }
复制代码

 

参考资料

  Codeforces Round #835 (Div. 4) Editorial:https://codeforces.com/blog/entry/109348

posted @   onlyblues  阅读(101)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示