Official

E - トーナメント分割の均衡グループ / Balanced Groups in Tournament Partition Editorial by kyopro_friends


文字 0, 1 と、それを数値として解釈したときの値を同一視します。

\(N\) 人からなるグループが均衡グループであることは、その区間の和が \(N/2\) であることと同値です。よって、均衡グループであるかどうかを判定するためには、区間和が分かればよさそうです。

\(S\) に対して \(l\) を選んで操作してできる文字列 \(T_l\) とします。

\(T_l\)\(T_{l+1}\) は、「\(l\) 文字目」「\(l+K\) 文字目」「区間内の 0, 1 の境界」の高々 \(4\) 文字しか違いません。

図

よって \(T_l\)\(T_{i+1}\) で均衡グループかどうかを再考慮する必要があるグループの個数は高々 \(4N\) 個です。それぞれについて区間和の変化を調べることで、均衡グループの個数の変化を求めることができます。

例:4文字目を 0 から 1 に変更した際の、各グループに対応する区間の区間和の更新

図

以上を元に差分更新を行うことで、\(l\) の昇順に全体として \(O(2^N N)\) 時間でこの問題を解くことができます。

実装例 (C++)

#include<bits/stdc++.h>
using namespace std;

int main(){
  int n, k;
  cin >> n >> k;
  string ss;
  cin >> ss;
  vector<int> s(1<<n);
  for(int i=0; i<1<<n; i++){
    if(ss[i] == '0'){
      s[i] = 0;
    }else{
      s[i] = 1;
    }
  }
  
  // 操作をしない場合
  int ans = 0;
  for(int w=0; w<=n; w++){
    for(int i=0; i<1<<n; i+=1<<w){
      if(accumulate(s.begin()+i, s.begin()+i+(1<<w), 0) * 2 == (1<<w)){
        ans++;
      }
    }
  }
  
  // l=0で操作を行い、区間内の0の個数と、各グループの総和・均衡グループの個数を記録
  vector<int> t=s;
  sort(t.begin(), t.begin()+k);
  int count0 = count(t.begin(), t.begin()+k, 0);
  vector<vector<int>>count(n+1);
  int crr = 0;
  for(int w=0; w<=n; w++){
    for(int i=0; i<1<<n; i+=1<<w){
      int c = accumulate(t.begin()+i, t.begin()+i+(1<<w), 0);
      count[w].push_back(c);
      if(c * 2 == (1<<w)){
        crr++;
      }
    }
  }

  auto upd=[&](int i, int v){
    // T[i] を v に変更し、各グループの総和・均衡グループの個数を更新
    if(t[i] == v){
      return;
    }
    for(int w=0; w<=n; w++){
      int ii = i>>w;
      if(count[w][ii] * 2 == (1<<w)){
        crr--;
      }
      count[w][ii] -= t[i];
      count[w][ii] += v;
      if(count[w][ii] * 2 == (1<<w)){
        crr++;
      }
    }
    t[i] = v;
  };
  
  for(int i=0; i<(1<<n)-k; i++){
    // [i,i+k) から [i+1,i+k+1) に変更
    // i を削除
    if(s[i] == 0){
      count0--;
    }
    if(t[i] != s[i] && count0 != 0){
      upd(i, 1);
      upd(i+count0, 0);
    }
    //i+k を追加
    if(s[i+k] == 0){
      count0++;
    }
    if(s[i+k] == 0 && count0 != k){
      upd(i+count0, 0);
      upd(i+k, 1);
    }
    ans = max(ans, crr);
  }
  cout << ans << endl;
}

実装例 (Python)

N, K = map(int, input().split())
S = list(map(int, input()))

# 操作をしない場合
ans = 0
for w in range(N+1):
  for i in range(0, 2**N, 2**w):
    if sum(S[i:i+2**w]) * 2 == 2**w:
      ans += 1

# l=0で操作を行い、区間内の0の個数と、各グループの総和・均衡グループの個数を記録
T = list(S)
T[:K] = sorted(T[:K])
count0 = K - sum(T[:K])
count = [[] for _ in range(N+1)]
crr = 0
for w in range(N+1):
  for i in range(0, 2**N, 2**w):
    c = sum(T[i:i+2**w])
    count[w].append(c)
    if c * 2 == 2**w:
      crr += 1

def upd(i, v):
  # T[i] を v に変更し、各グループの総和・均衡グループの個数を更新
  global crr
  if T[i] == v:
    return
  for w in range(N+1):
    ii = i // (2**w)
    if count[w][ii] * 2 == 2**w:
      crr -= 1
    count[w][ii] -= T[i]
    count[w][ii] += v
    if count[w][ii] * 2 == 2**w:
      crr += 1
  T[i] = v

for i in range(2**N-K):
  # [i,i+K) から [i+1,i+K+1) に変更
  # i を削除
  if S[i] == 0:
    count0 -= 1
  if T[i] != S[i] and count0 != 0:
    upd(i, 1)
    upd(i+count0, 0)
  # i+K を追加
  if S[i+K] == 0:
    count0 += 1
  if S[i+K] == 0 and count0 != K:
      upd(i+count0, 0)
      upd(i+K, 1)
  ans = max(ans, crr)

print(ans)

posted:
last update: