Flipping Game

CF327A Flipping Game

小I有些无聊,所以他发明了一个在纸上玩的游戏。

他写下了n个整数a1,a2,a3,a4......an,每个都是0或1中的一个。他被允许做如下的一次操作:他选择一个起点i,一个终点j,保证1<=i<=j<=n,然后将区间中的每一个数翻转。翻转指将ax的值设定为1-ax。

问:翻转一次后,最多有几个1.

题目描述

Iahub got bored, so he invented a game to be played on paper.

He writes $ n $ integers $ a_{1},a_{2},...,a_{n} $ . Each of those integers can be either 0 or 1. He's allowed to do exactly one move: he chooses two indices $ i $ and $ j $ ( $ 1<=i<=j<=n $ ) and flips all values $ a_{k} $ for which their positions are in range $ [i,j] $ (that is $ i<=k<=j $ ). Flip the value of $ x $ means to apply operation $ x=1 $ - $ x $ .

The goal of the game is that after exactly one move to obtain the maximum number of ones. Write a program to solve the little game of Iahub.

输入格式

The first line of the input contains an integer $ n $ ( $ 1<=n<=100 $ ). In the second line of the input there are $ n $ integers: $ a_{1},a_{2},...,a_{n} $ . It is guaranteed that each of those $ n $ values is either 0 or 1.

输出格式

Print an integer — the maximal number of 1s that can be obtained after exactly one move.

输入输出样例 #1

输入 #1

5
1 0 0 1 0

输出 #1

4

输入输出样例 #2

输入 #2

4
1 0 0 1

输出 #2

4

说明/提示

In the first case, flip the segment from 2 to 5 $ (i=2,j=5) $ . That flip changes the sequence, it becomes: [1 1 1 0 1]. So, it contains four ones. There is no way to make the whole sequence equal to [1 1 1 1 1].

In the second case, flipping only the second and the third element $ (i=2,j=3) $ will turn all numbers into 1.


CF 327A — Flipping Game(思路笔记)

题意与目标

给定一个 0/1 数组,必须选择一个连续区间 [i, j] 把其中每个元素取反(0↔1),使得最终数组中的 1 的个数最大。输出最大值。


关键转化:把“翻转”变成“收益”

翻转某段时:

  • 0 → 1:对答案的增量 +1
  • 1 → 0:对答案的增量 −1

据此构造收益数组 b

  • b[k] = +1,若 a[k] = 0
  • b[k] = −1,若 a[k] = 1

记原数组 1 的个数为 ones。若翻转区间为 [i, j],这段带来的总收益就是 sum(b[i..j])
所以答案为:
最后 answer = ones + best


边界与特判

  • 全是 1:因为必须翻一次,无论翻哪一段都会至少损失 1 个 1,最优是 n-1
    在收益数组里表现为 b ≡ -1,最大子段和 best = -1,代入 ones + best = n - 1
  • n = 1
    • [1] → 翻后 0,答案 0
    • [0] → 翻后 1,答案 1
      一般公式也能正确给出。

复杂度

  • 时间:O(n)(一次线性扫描)
  • 空间:O(1)(Kadane)或 O(1) 额外空间(前缀和用滚动变量)

示例快走一遍

样例 1

a = [1, 0, 0, 1, 0]ones = 2
b = [-1, +1, +1, -1, +1]
最大子段和取 [2..5](+1)+(+1)+(-1)+(+1) = +2
answer = 2 + 2 = 4

样例 2

a = [1, 0, 0, 1]ones = 2
b = [-1, +1, +1, -1]
最大子段和取 [2..3]+2
answer = 2 + 2 = 4


碰到“类似题”的通用套路

  1. 翻转一段 0/1,使某个计数最大
    • 把翻转带来的增量写成逐点收益,0→+1、1→−1(或按目标权重设定),做最大子段和
  2. 把一段取反/替换,使某种函数最优
    • 对每个位置写出“操作后贡献 − 操作前贡献”作为收益,再做最大子段和。
  3. 允许“最多一次”操作
    • 若最大子段和 ≤ 0,则不操作(与本题不同,本题“必须一次”)。
  4. 必须/最多进行 k 次(k>1)
    • 变成“选 k 个不相交子段收益最大”,用 DP(分段最大和,O(nk))解决。
  5. 把最多 k 个 0 变 1,使连续 1 最长
    • 属于滑动窗口:窗口内 0 的个数 ≤ k,维护最大长度。

常见坑 & 自检清单

  • ✅ 一定要在收益数组 b上求最大子段和,而不是直接在 a 上。
  • ✅ 记住本题是必须翻一次 → Kadane/前缀和求的是非空子段。
  • ✅ 全 1 时返回 n-1(等价于 ones + (-1))。
  • ✅ 细节初始化正确:Kadane 的第一项,或前缀和的 min_prefP[0]
  • n=1 的极小样例符合预期。


/*
  修正点:
  1) 构造收益串 b:a[i]==0 -> +1,a[i]==1 -> -1
  2) 在 b 上做最大子段和(Kadane/DP),并正确初始化 f[1]
  3) ones==n 时必须翻一次 → 答案 n-1,直接返回,避免多次输出
  4) 只输出一次:answer = ones + Max
*/
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 110;
int n, a[N];
int b[N];  // 收益串
int f[N];  // f[i]:以 i 结尾的最大子段和(非空)

int main() {
    cin >> n;
    int ones = 0;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        ones += a[i];
    }

    // 必须做一次翻转:全是 1 时最优只能变成 n-1
    if (ones == n) {
        cout << n - 1 << '\n';
        return 0;
    }

    // 构造收益串:0 -> +1,1 -> -1
    for (int i = 1; i <= n; i++) b[i] = (a[i] == 0 ? 1 : -1);

    // 在 b 上做最大子段和(Kadane/DP 版),注意初始化 f[1]
    f[1] = b[1];
    int Max = f[1];
    for (int i = 2; i <= n; i++) {
        f[i] = max(b[i], f[i - 1] + b[i]);
        Max = max(Max, f[i]);
    }

    // 此时至少有一个 0,Max >= 1
    cout << (ones + Max) << '\n';
    return 0;
}
posted @ 2025-09-18 16:36  啦啦啦456123  阅读(16)  评论(0)    收藏  举报