公式

K - 商店街の区画選び / Choosing Blocks in a Shopping Street 解説 by kyopro_friends


この問題は平方分割により解くことができます。

問題の読み替え

\(A'_i=\sum_{i=l}^{l+K-1}A_i\)
\(C'_i=\sum_{i=l}^{l+K-1}C_i\)

と定めます。これは適当な実装により \(O(N)\) で求めることができます。この \(A',C'\) を用いることで、問題は次のように読み替えられます。

2 種類のクエリを処理せよ

  • 更新クエリ:\(L,R,x\) が与えられる。\(A'_L,A'_{L+1},\ldots,A'_R\)\(x\) を加える

  • 質問クエリ:\(X\) が与えられる。\(C'_i\geq X\) を満たす \(i\) についての、\(A'_i\) の最小値を求めよ。

平方分割によるデータの持ち方

読み替えた問題を平方分割を用いて解きます。分割によって生まれる \(\Theta(\sqrt{N})\) 個の要素からなる区間をバケットと呼ぶことにします。バケットでは次の要素を持ちます。

  • 更新クエリによりバケット全体の \(A'_i\) に加算されている値 lazy
  • \(k\) について「\(C'_i\geq k\) であるような \(i\) についての \(A'_i\) の最小値」 Amin[k]

注意として、バケットが持つ要素の個数は \(O(\sqrt{N})\) であることから、\(C'_i\) の種類数も \(O(\sqrt{N})\) であり、「\(C'_i\geq k\) であるような \(i\) についての \(A'_i\) の最小値」が意味のある情報を持つ \(k\)\(O(\sqrt{N})\) 種類です。さらに、\(C_i\)\(0\)\(1\) であることから、それらは連続した値になります。よって、\(C'_i\) ことに \(A'_i\) の 最小値を求めたあと、累積 min を取ることで、\(O(\sqrt{N})\) で初期化を行うことができます。

質問クエリの処理

全てのバケットについての Amin[X] + lazy の最小値が答えです。各バケットのこの値は \(O(1)\) で取得できるので、 \(O(\sqrt{N})\) でこのクエリを処理することができます。

更新クエリの処理

更新範囲にバケット全体が含まれる場合は lazy の値のみを書き換えます。 \(O(\sqrt{N})\) 個のバケットを \(O(1)\) で更新するので全体で \(O(\sqrt{N})\) です。

そうでない場合は、 \(A'_i\) および Amin[k] を再計算します。高々 2 個のバケットを \(O(\sqrt{N})\) で更新するので全体で \(O(\sqrt{N})\) です。(lazy は作用させてしまっても、そのまま残しても構いません)

よってこのクエリを \(O(\sqrt{N})\) で処理することができました。

まとめ

以上より、質問クエリ・更新クエリをともに \(O(\sqrt{N})\) で処理することができるため、この問題を \(O(Q\sqrt{N})\) で解くことができました。

実装例 (C++)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r)for(int i=(l);i<(r);i++)
using ll = long long;
void chmin(ll&x,ll y){x = min(x,y);}
const ll INF = 1e18;
const int B = 300;

struct block{
  ll lazy;
  int cc_min;
  vector<ll> Amin;  // Amin[x]=min{asum[i] | csum[i]>=x+cc_min}
};

int main(){
  int n, k, q;
  cin >> n >> k >> q;
  vector<int> a(n), c(n);
  rep(i,0,n) cin >> a[i];
  rep(i,0,n) cin >> c[i];
  
  // sum(a[i:i+k]), sum(c[i:i+k]) の計算
  int N = n - (k - 1);
  vector<ll> asum(N);
  vector<int> csum(N);
  ll aa = 0;
  int cc = 0;
  rep(i,0,k-1){
    aa += a[i];
    cc += c[i];
  }
  rep(i,k-1,n){
    aa += a[i];
    asum[i-(k-1)] = aa;
    aa -= a[i-(k-1)];
    cc += c[i];
    csum[i-(k-1)] = cc;
    cc -= c[i-(k-1)];
  }

  int n_blocks = (N + B - 1) / B;
  vector<block> blocks(n_blocks);
  
  auto build_block = [&](int ii){
    int L = ii * B;
    int R = min(L + B, N);
    auto min_max_it = minmax_element(csum.begin() + L, csum.begin() + R);
    if(blocks[ii].Amin.size() == 0){
      // 初回
      blocks[ii].cc_min = *min_max_it.first;
      int cc_max = *min_max_it.second;
      blocks[ii].Amin = vector<ll>(cc_max - blocks[ii].cc_min + 1, INF);
    }else{
      // 2回目以降
      blocks[ii].Amin = vector<ll>(blocks[ii].Amin.size(), INF);
    }
    rep(i,L,R){
      int idx = csum[i] - blocks[ii].cc_min;
      chmin(blocks[ii].Amin[idx], asum[i]);
    }
    for(int i=blocks[ii].Amin.size()-1; i>0; i--){
      chmin(blocks[ii].Amin[i-1], blocks[ii].Amin[i]);
    }
  };
  
  
  auto update=[&](int l, int r, int v){
    // 更新クエリ
    if(l % B != 0){
      // 左の端数
      int L = l;
      int R = min(L / B * B + B, r);
      rep(i,L,R){
        asum[i] += v;
      }
      build_block(l / B);
      l = R;
    }
    if(l < r && r % B != 0){
      // 右の端数
      int L = r / B * B;
      int R = r;
      rep(i,L,R){
        asum[i] += v;
      }
      build_block(r / B);
      r = L;
    }
    if(l < r){
      // 真ん中
      int LL = l / B;
      int RR = r / B;
      rep(ii,LL,RR){
        blocks[ii].lazy += v;
      }
    }
  };
  
  auto query = [&](int x){
    // 質問クエリ
    ll ans = INF;
    for(auto&block: blocks){
      int p = max(0, x - block.cc_min);
      if(p < block.Amin.size()){
        chmin(ans, block.Amin[p] + block.lazy);
      }
    }
    return ans;
  };
  
  rep(ii,0,blocks.size()){
    build_block(ii);
  }

  rep(_,0,q){
    int t, x, y;
    cin >> t >> x >> y;
    if(t == 1){
      x--;
      int l = max(0, x - (k - 1));
      int r = min(N, x + k - (k - 1));
      update(l, r, y - a[x]);
      a[x] = y;
    }else{
      ll ans = query(x);
      if(ans == INF){
        cout << "IMPOSSIBLE" << endl;
      }else{
        cout << ans << endl;
      }
    }
  }
}

実装例 (Python)

INF = 10**18
B = 300

class Block:
  def __init__(self):
    self.lazy = 0
    self.cc_min = -1
    self.Amin = []  # Amin[x]=min{asum[i] | csum[i]>=x+cc_min}

n, k, q = map(int, input().split())
a = list(map(int, input().split()))
c = list(map(int, input().split()))
N = n - (k - 1)

# sum(a[i:i+k]), sum(c[i:i+k]) の計算
asum = [0] * N
csum = [0] * N
aa = sum(a[:k-1])
cc = sum(c[:k-1])
for i in range(k - 1, n):
  aa += a[i]
  asum[i-(k-1)] = aa
  aa -= a[i-(k-1)]
  cc += c[i]
  csum[i-(k-1)] = cc
  cc -= c[i-(k-1)]

n_blocks = (N + B - 1) // B
blocks = [Block() for _ in range(n_blocks)]

def build_block(ii):
  L = ii * B
  R = min(L + B, N)
  if len(blocks[ii].Amin) == 0:
    # 初回
    blocks[ii].cc_min = min(csum[L:R])
    cc_max = max(csum[L:R])
    blocks[ii].Amin = [INF] * (cc_max - blocks[ii].cc_min + 1)
  else:
    # 2回目以降
    cc_min = blocks[ii].cc_min
    blocks[ii].Amin = [INF] * len(blocks[ii].Amin)

  for i in range(L, R):
    idx = csum[i] - blocks[ii].cc_min
    if blocks[ii].Amin[idx] > asum[i]:
      blocks[ii].Amin[idx] = asum[i]

  for i in range(len(blocks[ii].Amin) - 1, 0, -1):
    if blocks[ii].Amin[i - 1] > blocks[ii].Amin[i]:
      blocks[ii].Amin[i - 1] = blocks[ii].Amin[i]

def update(l, r, v):
  # 更新クエリ
  if l % B:
    # 左の端数
    L = l
    R = min(l // B * B + B, r)
    for i in range(L, R):
      asum[i] += v
    build_block(l // B)
    l = R
  if l < r and r % B:
    # 右の端数
    L = r // B * B
    R = r
    for i in range(L, R):
      asum[i] += v
    build_block(r // B)
    r = L
  if l < r:
    # 真ん中
    LL = l // B
    RR = r // B
    for ii in range(LL, RR):
      blocks[ii].lazy += v

def query(x):
  # 質問クエリ
  ans = INF
  for block in blocks:
    p = max(0, x - block.cc_min)
    if p >= len(block.Amin):
      continue
    ans = min(ans, block.Amin[p] + block.lazy)
  return ans

for ii in range(n_blocks):
  build_block(ii)

for _ in range(q):
  t, x, y = map(int, input().split())
  if t == 1:
    x -= 1
    l = max(0, x - (k - 1))
    r = min(N, x + k - (k - 1))
    update(l, r, y - a[x])
    a[x] = y
  else:
    ans = query(x)
    if ans == INF:
      print("IMPOSSIBLE")
    else:
      print(ans)

投稿日時:
最終更新: