CF1630D Flipping Range 解题报告
思路
首先考虑记 \(x=\gcd(b_1,b_2,...,b_m)\)。那么 \(x\) 可以表示为 \(\sum (q_i \times b_i),q_i \in \mathbb{Z}\),所以我们一定能通过若干次翻转使得其等价于只翻连续 \(x\) 个数。
于是问题转化为有 \(n\) 个整数,可以进行无限次操作,一次操作可以选择连续的 \(x\) 个数变为它的相反数,求最大可能 \(\sum a\)。
注意到翻 \(x\) 个数有些麻烦,考虑一个更强的操作。如果我们连续翻转 \([i,i+x],[i+1,i+x+1]\),就相当于我们只翻转了 \(i\) 和 \(i+x+1\) 两个数。由此一来,我们就获得了同时翻相距为 \(x\) 的两个数的操作。
接下来我们将 \(a\) 按照下标对 \(x\) 同余分成 \(x\) 个小序列 \(c_0,c_1,...,c_{n-1}\)。即 \(1,x+1,2x+1...\) 成为一个小序列,\(2,x+2,2x+2\) 成为一个小序列……。对于每个小序列 \(c_i\),相当于可以进行 \(x=2\) 的操作。
然后我们考虑对于每个 \(c_i\) 寻找最优解,目标是让负数剩的尽可能小(不一定是少,是加和小)。我们可以使原来有奇数个负数的序列只剩一个负数,原来有偶数个负数的序列一个负数都不剩。方法是从左向右扫 \(c\),遇到一个负数就将它和它右面那个数翻过来,这样就实现了至多只剩一个负数。如果还剩一个负数,就不断翻它和它左面的那个数,直至负号移到了绝对值最小的那个数上。
但是由于我们用了一个过强的操作保证每个 \(c_i\) 都取到了最优解,我们还需要保证 \(c\) 之间关系最优才能取得全局最优解。\(c\) 之间关系无非就是是否剩下一个负数,也就是每个 \(c_i\) 负数个数的奇偶性。但注意到在 \(a\) 中每进行一次操作,每个 \(c\) 负数个数的奇偶性都会被恰好改变。原来剩下一个负数的 \(c\) 现在不会剩下负数,原来不剩负数的 \(c\) 现在会剩下一个负数。也就是说每个 \(c\) 负数个数的奇偶性一共就两种情况,都统计一下求个最优值。最后拿全局绝对值和减去两倍它即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5;
int n, m, b[N], a[N], T, cnt1, cnt2, x, sum, ans;
void solve()
{
scanf("%lld%lld", &n, &m);
cnt1 = cnt2 = sum = 0;
for(int i = 1; i <= n; i ++)
{
scanf("%lld", &a[i]);
sum += abs(a[i]);
}
for(int i = 1; i <= m; i ++)
{
scanf("%lld", &b[i]);
}
x = b[1];
for(int i = 2; i <= m; i ++)
{
x = __gcd(x, b[i]);
}
for(int i = 1; i <= x; i ++)
{
int u = 2e18, v = 0;
for(int j = i; j <= n; j += x)
{
if(a[j] < 0)
{
v ++;
}
u = min(u, abs(a[j]));
}
if(v % 2 == 1)
{
cnt1 += u;
}
else
{
cnt2 += u;
}
}
ans = sum - 2 * min(cnt1, cnt2);
printf("%lld\n", ans);
}
signed main()
{
cin >> T;
while(T --)
{
solve();
}
}

浙公网安备 33010602011771号