E. Binary Inversions
E. Binary Inversions
You are given a binary array of length . 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 into a 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 such that and .
Input
The input consists of multiple test cases. The first line contains an integer () — the number of test cases. The description of the test cases follows.
The first line of each test case contains an integer () — the length of the array.
The following line contains space-separated positive integers () — the elements of the array.
It is guaranteed that the sum of over all test cases does not exceed .
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 (), (), (), being a total of , which already is the maximum possible.
For the second test case, the inversions are initially formed by the pairs of indices (), (), (), (), being a total of four. But, by flipping the first element, the array becomes , which has the inversions formed by the pairs of indices (), (), (), (), (), (), () which total to inversions which is the maximum possible.
解题思路
注意到这题本质就是问在最多置换一位的情况下,序列的最大逆序对个数是多少。
前缀后缀和
先给出最容易想到的做法。
这种做法是对暴力枚举的优化,把求改变后的序列的逆序对个数优化到。维护序列前缀中 1 的个数和维护序列后缀中 0 的个数,同时假设原序列的逆序对个数为,然后枚举每一位,
- 如果,那么要把置换成,此时改变后的序列的逆序对个数应该是,原来总的逆序对个数减去第个位置的逆序对个数(即第个位置之前的个数,也就是),由于变成了因此第个位置之后所有是的位置的逆序对个数都增加了个,所以还要加上(表示第个位置之后的个数)。所以变化后的序列的逆序对个数就是。
- 如果,那么要把置换成,此时改变后的序列的逆序对个数应该是,原来总的逆序对个数加上第个位置的逆序对个数,由于变成了因此第个位置之后所有是的位置的逆序对个数都减少了个,所以还要减去。所以变化后的序列的逆序对个数就是。
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 }
贪心
比赛的时候虽然是猜出来的做法,但我感觉并不直观,官方题解也没给出这种做法的正确性证明,因此来补充一下较为严格的证明。
先说结论,答案就是对下面三种情况取最大值:
- 统计次操作时(即原序列)逆序对的个数。
- 从左往右找到第一次出现的,置换成,再求逆序对的个数。
- 从右往左找到第一次出现的,置换成,再求逆序对的个数。
其实是先猜做法再证明正确性的。容易想到最暴力的做法是枚举每一位并置换,然后对新的序列求逆序对。这种做法的时间复杂度是,会超时。
无非就是把每一个变成求一次逆序对,或者是把每一个变成求一次,因此如果要优化的话容易想到是否存在某个位置的,在置换这个位置的后得到的逆序对个数比置换其他位置的要多(同理)。
假设序列中的个数为,并假设 ,表示序列中第个左边的个数(理解为这个位置的逆序对个数),容易知道是个递增序列。
当不执行操作时,整个序列的逆序对个数就是。现在置换第个位置的 ,那么逆序对个数就变成。注意到是定值,而是变化的量,由于随着的递增,会递增,因此会递减,因此会递减,因此要使得逆序对最大,那么应该取最小值,即应该置换第一个的位置。
同理证明置换成的情况,我们只需注意到求逆序对还可以根据的位置往右统计的个数(上面的做法是根据的位置统计左边的个数,这两种统计方法的等价的)。那么我们reverse整个序列,此时就变成了根据的位置统计左边的个数,根据上面的证明可以知道应该置换左边第一个的位置能够得到最大逆序对,注意由于这是对于reverse后的序列的说法,因此对于原序列应该是置换最后一个的位置。
最后对这三种情况取最大值就可以了。
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
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16917331.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效