题解:AtCoder AT_awc0081_e Balanced Groups in Tournament Partition

【题目来源】

AtCoder:E - Balanced Groups in Tournament Partition

【题目描述】

\(2^N\) students are standing in a row from left to right, and each student is wearing either a red hat or a white hat. The hat colors of the students are represented by a string \(S\) of length \(2^N\). For the \(i\)-th student from the left, if the \(i\)-th character of \(S\) is 0, they are wearing a red hat, and if it is 1, they are wearing a white hat.

Takahashi, in preparation for the sports festival, can perform the following operation at most once:

  • Choose an integer \(l\) (\(1 \leq l \leq 2^N - K + 1\)), and rearrange the \(K\) consecutive students from the \(l\)-th to the \((l+K-1)\)-th from the left. As a result of the rearrangement, all students with red hats within the interval are placed consecutively on the left side of the interval, and all students with white hats are placed consecutively on the right side of the interval. The positions of students outside the interval do not change.

After the operation (or as-is if no operation is performed), the students are recursively divided into groups according to the following rules:

  1. Initially, all students from the \(1\)-st to the \(2^N\)-th from the left form one group.
  2. A group with \(2\) or more people is split into two groups: one consisting of the first half of students (counted from the left) and one consisting of the second half.
  3. This splitting is repeated until each group contains exactly \(1\) person.

Consider all groups that appear during this process (including the initial whole group, groups at each intermediate stage, and the final single-person groups — a total of \(2^{N+1} - 1\) groups). A group is called a "balanced group" if the number of students with red hats and the number of students with white hats within the group are equal. Note that a single-person group cannot satisfy this condition, so it is never a balanced group.

Find the maximum possible number of balanced groups when Takahashi optimally chooses whether to perform the operation and the position of the interval.

\(2^N\) 名学生从左到右站成一排,每名学生戴着一顶红帽子或白帽子。学生的帽子颜色由一个长度为 \(2^N\) 的字符串 \(S\) 表示。对于从左数第 \(i\) 名学生,如果 \(S\) 的第 \(i\) 个字符是 0,则他戴红帽子;如果是 1,则戴白帽子。

高桥为准备体育节,最多可以进行一次如下操作:

  • 选择一个整数 \(l\)\(1 \leq l \leq 2^N - K + 1\)),并重新排列从第 \(l\) 名到第 \((l+K-1)\) 名学生的连续 \(K\) 名学生。重新排列后,区间内所有戴红帽子的学生被连续地放置在区间的左侧,所有戴白帽子的学生被连续地放置在区间的右侧。区间外的学生位置不变。

操作后(或不进行操作),学生按照以下规则递归地分组:

  1. 初始时,从左数第 \(1\) 名到第 \(2^N\) 名学生组成一个组。
  2. 一个包含 \(2\) 人以上的组被分割成两个组:一个由前一半学生(从左数)组成,另一个由后一半学生组成。
  3. 重复此分割,直到每个组恰好包含 \(1\) 人。

考虑在这个过程中出现的所有组(包括初始的整个组、每个中间阶段的组以及最终的单人组——总共 \(2^{N+1} - 1\) 个组)。如果一个组内戴红帽子的学生数量和戴白帽子的学生数量相等,则该组被称为“平衡组”。注意,单人组不可能满足此条件,因此永远不会是平衡组。

求当高桥最优地选择是否进行操作以及区间位置时,可能得到的最大平衡组数量。

【输入】

\(N\) \(K\)
\(S\)

The first line contains the integer \(N\), which represents the parameter for the number of students, and the integer \(K\), which represents the length of the interval to be rearranged in the operation, separated by a space. The second line contains the string \(S\) of length \(2^N\) representing the hat color of each student.

【输出】

Output the maximum number of balanced groups as a single integer.

【输入样例】

2 2
0110

【输出样例】

3

【核心思想】

  1. 问题分析:给定长度为 \(2^N\) 的二进制串 \(S\)0 表示红帽子,1 表示白帽子),最多进行一次操作:选择长度为 \(K\) 的区间,将区间内所有 0 移到左边,1 移到右边。然后将整个序列递归二分分组(完全二叉树结构),求平衡组(01 数量相等)的最大数量。这是一个线段树 + 滑动窗口问题,关键在于利用线段树维护区间和,并通过滑动窗口枚举操作位置来最大化平衡组数量。

  2. 算法选择

    • 线段树(Segment Tree):维护每个区间的和,节点值等于其子树叶子节点之和(即区间内 1 的数量)
    • 滑动窗口(Sliding Window):枚举所有可能的长度为 \(K\) 的操作区间,模拟操作后的效果
    • 完美二叉树性质:递归分组形成完全二叉树,线段树节点天然对应分组结构。第 \(d\) 层(从叶子向上)的节点对应大小为 \(2^d\) 的组
    • 层级计数val 从 1 开始,每层左移一位(1→2→4→8...),表示该层节点应包含的叶子数。若节点值等于 \(val/2\)(即一半 0 一半 1),则为平衡组
  3. 关键步骤

    • 初始化
      • 读取 \(N\)(二进制位数)、\(K\)(窗口大小)、\(S\)(二进制串)
      • siz = 1 << N,线段树大小为 \(2^N\)
      • 初始化线段树,根据 \(S\) 设置叶子节点(01
      • acc 统计当前平衡组数量
    • 处理初始状态
      • 统计前 \(K\) 个位置中 1 的数量 cnt
      • 模拟操作:将前 \(cnt\) 个位置设为 1,后 \(K-cnt\) 个位置设为 0
      • res = max(res, acc) 更新最大平衡组数
    • 滑动窗口枚举操作位置
      • 窗口从位置 \(K+1\) 滑动到 \(2^N\)
      • 移出窗口左端(位置 \(i-K\)):
        • 若 $S[i-K] = $ 1 且窗口未满,需要恢复该位置为 1,并调整窗口内 1 的分布
      • 移入窗口右端(位置 \(i\)):
        • 若 $S[i] = $ 0 且窗口非空,需要将该位置设为 0,并调整窗口内 1 的分布
      • 更新 cnt(窗口内 1 的数量)
      • res = max(res, acc) 更新最大平衡组数
    • 输出答案res
  4. 时间/空间复杂度

    • 时间复杂度:\(O(2^N \times N)\),共有 \(O(2^N)\) 个窗口位置,每次更新沿树高 \(O(N)\)
    • 空间复杂度:\(O(2^N)\),线段树需要 \(2 \times 2^N\) 的空间
  5. 线段树 + 滑动窗口的核心思想

    • 完美二叉树与线段树的对应:递归二分分组天然形成完全二叉树,线段树的每个节点正好对应一个组。通过维护区间和,可以快速判断每个组是否为平衡组
    • 层级条件判断:对于大小为 \(2^d\) 的组(第 \(d\) 层),平衡条件为节点值(1 的数量)等于 \(2^{d-1}\)(即一半 0 一半 1)。val 每层翻倍,通过比较 tr[i] == val/2 判断平衡
    • 滑动窗口模拟操作:操作的本质是将区间内所有 0 移到左边,1 移到右边。这等价于将区间内的 1 集中到右侧。通过滑动窗口和单点更新,可以高效模拟这一操作对所有组的影响
    • 增量更新维护平衡组数:每次单点更新时,自底向上更新线段树,同时维护 acc。若某节点在更新前后跨越了平衡条件,则相应增减 acc
    • 适用于完全二叉树结构、区间操作模拟、组合优化类问题

【算法标签】

线段树

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 1<<20 + 5;
int n, m, siz;  // n: 二进制位数,m: 窗口大小,siz: 线段树大小
string s;  // 输入字符串
int tr[N<<1];  // 线段树数组
int acc;  // 满足条件的子集数量

void update(int i, int x)  // 更新线段树
{
    i--;
    tr[i |= siz] = x;  // 定位到叶子节点
    int val = 1;  // 初始值
    while (i>>=1)  // 向上更新
    {
        if (tr[i]==val)  // 如果当前节点原本满足条件
            acc--;  // 减少计数
        tr[i] = tr[i<<1] + tr[i<<1|1];  // 更新节点值
        if (tr[i]==val)  // 如果更新后满足条件
            acc++;  // 增加计数
        val <<= 1;  // 更新val
    }
}
int main()
{
    cin >> n >> m;  // 输入位数和窗口大小
    cin >> s;  // 输入字符串
    s = " " + s;  // 在字符串前加空格,使下标从1开始
    siz = 1<<n;  // 计算线段树大小
    int res = 0, cnt = 0;  // 结果和当前窗口内1的数量

    for (int i=1; i<=siz; i++)  // 初始化
    {
        if (s[i]=='0')  // 如果为0
            continue;
        update(i, 1);  // 更新为1
        if (i<=m)  // 如果在窗口内
            cnt++;  // 计数
    }

    res = max(res, acc);  // 更新结果
    for (int i=1; i<=m-cnt; i++)  // 调整前m个位置
        update(i, 0);

    for (int i=m-cnt+1; i<=m; i++)  // 调整前m个位置
        update(i, 1);
    res = max(res, acc);  // 更新结果

    for (int i=m+1; i<=siz; i++)  // 滑动窗口
    {
        if (s[i-m]=='1' && cnt!=m)  // 如果移出的为1且窗口未满
        {
            update(i-m, 1);  // 更新移出位置
            update(i-cnt, 0);  // 更新移入位置
        }
        if (s[i-m]=='1')  // 如果移出的为1
            cnt--;  // 计数减少
        if (s[i]=='0' && cnt!=0)  // 如果移入的为0且窗口不为空
        {
            update(i-cnt, 0);  // 更新移入位置
            update(i, 1);  // 更新移出位置
        }
        if (s[i]=='1')  // 如果移入的为1
            cnt++;  // 计数增加
        res = max(res, acc);  // 更新结果
    }
    cout << res << endl;  // 输出结果
    return 0;
}

【运行结果】

2 2
0110
3
posted @ 2026-06-15 09:50  团爸讲算法  阅读(8)  评论(0)    收藏  举报