Beginning
这道题思维难度很大,有两个难点其实都不好解决,但因为其代码太过弱智所以只是绿题。
本题解详细地分析了做题时的历程与思路,所以希望大家可以仔细地完整阅读。
Analysis
首先先大体观察一下题目的性质:nnn 是排列,这可能会有用;题目的权值求法其实就是逆序对,而逆序对产生的贡献一定有偶数个,所以我们的答案一定是偶数。
同时,对二取模也十分引人注意。这意味着我们的 vvv 序列其实是一个 010101 序列。
题目要求可以选择执行一次移动操作使得权值和变大,也就是我们要通过这一次操作尝试变出更多的 111。尝试一下发现,每次移动一个数只会对其移动中跨过的数产生影响,如果是 010101 序列的话就体现在会把 010101 翻转。对于移动的数字,如果跨过了奇数个位置则翻转,否则不变。
可以简单理解一下:
- 如果一个数 xxx 从位置 aaa 移动到位置 bbb 和 b+1b+1b+1 之间(满足 a<ba<ba<b),那么对于 [a+1,b][a+1,b][a+1,b] 中的任意一个数字 ttt,如果 xxx 在移动前对 ttt 产生了贡献,则 x>tx>tx>t,移动后 xxx 将不再对 ttt 产生贡献,在模二的意义下就相当于 ttt 异或上 111,即 010101 翻转。反之亦然。
- 对于 xxx 同理,其移动后原来产生贡献的数字一定不产生贡献,原来不产生贡献的数字一定会产生新贡献。看贡献变化量的奇偶性,如果跨过偶数个那么贡献变化量一定是偶数(偶数 - 偶数 = 偶数),在模二意义下贡献值不变。反之亦然。
我们好像得到了一个初步的简化题面:给定一个 010101 序列,可以翻转某一个区间,使得最终序列中 111 个数最多。
显然,我们要求一个类似最大子段和的东西,然后用初始贡献加上一次翻转能带来的最大贡献就是答案。
同时我们还发现翻转区间的长度只能是偶数,因为上述 xxx 跨过奇数个位置的情况下 xxx 自己也会变,其实就等价于翻转了 [a,b][a,b][a,b] 这个长度为偶数的区间。
所以我们要找到一个长度为偶数的区间使得区间内 000 的个数减去 111 的个数得到的差最大。
考虑把 000 映射成 111,把 111 映射成 −1-1−1,然后统计前缀和。对于每个位置,可以记录下之前奇数位置与偶数位置上的最小前缀和,根据下标奇偶性转移最大差值即可。
时间复杂度 O(n)O(n)O(n)。
这还没完,转换完题意后我们还要考虑如何计算初始贡献。这道题时限卡树状数组的 log\loglog(实测光放一个 sort 交上去就会 TLE),所以我们还要考虑如何线性求初始贡献。
肯定要从对二取模这个性质下手。发现我们最开始得到的逆序对贡献数一定是偶数这个结论其实很对,假设对于 pip_ipi,因为是排列所以比 pip_ipi 小的数就有 pi−1p_i-1pi−1 个。设 tit_iti 表示 iii 左边比 pip_ipi 小的数的个数,根据题目贡献可以写成 [(i−1)−ti]+[(pi−1)−ti]=i+pi−(2ti+2)[(i-1)-t_i]+[(p_i-1)-t_i] = i+p_i - (2t_i+2)[(i−1)−ti]+[(pi−1)−ti]=i+pi−(2ti+2),后半部分明显被取模二约掉了!所以最终的初始贡献表达式即为 vi=(i+pi) mod 2v_i=(i+p_i) \bmod 2vi=(i+pi)mod2。
根据上述方法预处理即可,时间复杂度 O(n)O(n)O(n)。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e6 + 7;int n, a[maxn], s[maxn];void solve()
{int ans = 0;cin >> n;for(int i = 1; i <= n; i ++){cin >> a[i];a[i] = (i + a[i]) % 2; // 计算初始值ans += (a[i] == 1); // 统计答案s[i] = s[i - 1] + (a[i] == 0? 1 : -1); // 转化+预处理前缀和}int min1 = 1e9, min2 = 0; // 奇数、偶数位置的最小前缀和int maxd = 0; // 最大贡献for(int i = 1; i <= n; i ++){if(i % 2 == 0){maxd = max(maxd, s[i] - (i==2? 0:min2)); // 特判一下之前还没有前缀和的时候可以认为能全选(历史前缀和=0)min2 = min(min2, s[i]);}else{maxd = max(maxd, s[i] - (i==1? 0:min1));min1 = min(min1, s[i]);}}cout << ans + maxd << '\n';
}signed main()
{ios :: sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);int T; cin >> T;while(T --) solve();return 0;
}