You are given a binary array$^{\dagger}$ 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$^{\ddagger}$ the array can have after performing at most one operation?
$^\dagger$ A binary array is an array that contains only zeroes and ones.
$^\ddagger$ The number of inversions in an array is the number of pairs of indices $i,j$ such that $i<j$ and $a_i > a_j$.
InputThe input consists of multiple test cases. The first line contains an integer $t$ ($1 \leq t \leq {10}^{4}$) — the number of test cases. The description of the test cases follows.
The first line of each test case contains an integer $n$ ($1 \leq n \leq 2 \cdot {10}^{5}$) — the length of the array.
The following line contains $n$ space-separated positive integers $a_1, a_2, \ldots, a_n$ ($0 \leq a_i \leq 1$) — the elements of the array.
It is guaranteed that the sum of $n$ over all test cases does not exceed $2 \cdot {10}^{5}$.
OutputFor each test case, output a single integer — the maximum number of inversions the array can have after performing at most one operation.
Example input5 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 1output
3 7 1 13 2Note
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的个数,同时假设原序列的逆序对个数为$\text{sum}$,然后枚举每一位,
- 如果$a_i = 0$,那么要把$a_i$置换成$1$,此时改变后的序列的逆序对个数应该是,原来总的逆序对个数$\text{sum}$减去第$i$个位置的逆序对个数(即第$i$个位置之前$1$的个数,也就是${\text{pre}}_{i-1}$),由于$a_i$变成了$1$因此第$i$个位置之后所有是$0$的位置的逆序对个数都增加了$1$个,所以还要加上${\text{suf}}_{i+1}$(${\text{suf}}_{i+1}$表示第$i$个位置之后$0$的个数)。所以变化后的序列的逆序对个数就是$\text{sum} - {\text{pre}}_{i-1} + {\text{suf}}_{i+1}$。
- 如果$a_i = 1$,那么要把$a_i$置换成$0$,此时改变后的序列的逆序对个数应该是,原来总的逆序对个数$\text{sum}$加上第$i$个位置的逆序对个数,由于$a_i$变成了$0$因此第$i$个位置之后所有是$0$的位置的逆序对个数都减少了$1$个,所以还要减去${\text{suf}}_{i+1}$。所以变化后的序列的逆序对个数就是$\text{sum} + {\text{pre}}_{i-1} - {\text{suf}}_{i+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 }贪心
比赛的时候虽然是猜出来的做法,但我感觉并不直观,官方题解也没给出这种做法的正确性证明,因此来补充一下较为严格的证明。
先说结论,答案就是对下面三种情况取最大值:
- 统计$0$次操作时(即原序列)逆序对的个数。
- 从左往右找到第一次出现的$0$,置换成$1$,再求逆序对的个数。
- 从右往左找到第一次出现的$1$,置换成$0$,再求逆序对的个数。
其实是先猜做法再证明正确性的。容易想到最暴力的做法是枚举每一位并置换,然后对新的序列求逆序对。这种做法的时间复杂度是$O(n^2)$,会超时。
无非就是把每一个$0$变成$1$求一次逆序对,或者是把每一个$1$变成$0$求一次,因此如果要优化的话容易想到是否存在某个位置的$0$,在置换这个位置的$0$后得到的逆序对个数比置换其他位置的$0$要多(同理$1$)。
假设序列中$0$的个数为$x$,并假设$s[k]$ $(1 ≤ k ≤ x)$ ,表示序列中第$k$个$0$左边$1$的个数(理解为这个位置的逆序对个数),容易知道$s[k]$是个递增序列。
当不执行操作时,整个序列的逆序对个数就是$\sum\limits_{k=1}^{x}{s[k]}$。现在置换第$i$个位置的$0$ $(1 \leq i \leq x)$,那么逆序对个数就变成$\sum\limits_{k=1}^{x}{s[k]} + (x-i) - s[i]$。注意到$\sum\limits_{k=1}^{x}{s[k]} + x$是定值,而$-(i + s[i])$是变化的量,由于随着$i$的递增,$i + s[i]$会递增,因此$-(i + s[i])$会递减,因此$\sum\limits_{k=1}^{x}{s[k]}+ (x-i) - 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
评论列表