公式

C - 割引クーポン / Discount Coupon 解説 by kyopro_friends


この問題は俗にimos法と呼ばれる、累積和の逆操作を用いることで高速に求めることができます。

累積和は数列同士の和を保ちます。つまり、「数列 \(A\) の累積和と数列 \(B\) の累積和を足した数列」は「数列 \(A\) と数列 \(B\) を足した数列の累積和」になります。

                  差分 ←      → 累積和
A   0  1  0  0  1  0  0        0  0  1  1  1  2  2  2 
B   0  0 -1  0  0  2  0        0  0  0 -1 -1 -1  1  1
A+B 0  1 -1  0  1  2  0        0  0  1  0  0  1  3  3

また「ある区間の要素が \(1\) でそれ以外は全て \(0\)」という数列に対し、累積和の逆操作を行うと「\(1\)\(-1\)\(1\) 個ずつで他は全て \(0\) 」という数列になります。

    0   1   0   0   0  -1   0   0
            累積和↓   ↑差分
  0   0   1   1   1   1   0   0   0

よって「 \((0,\dots,0,D_i,\dots,D_i,0,\dots,0)\) の形の数列 \(M\) 個の和」は「\((0,\dots,0,D_i,0,\dots,0,-D_i,0,\dots,0)\) の形の数列 \(M\) 個の和の累積和」となり、これは \(O(N+Q)\) で求めることができます。

このように、累積和の逆操作を用いて計算を高速化する手法全般をimos法と呼びます。

実装例 (C++)

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

int main(){
  int n, q;
  cin >> n >> q;
  vector<int>w(n);
  for(int i=0; i<n; i++) cin >> w[i];
  vector<long long>s(n+1);
  for(int i=0; i<q; i++){
    int l, r, d;
    cin >> l >> r >> d;
    s[l-1] += d;
    s[r] -= d;
  }
  for(int i=0; i<n; i++){
    s[i+1] += s[i];
  }

  int ans = 0;
  for(int i=0; i<n; i++){
    if(s[i] >= w[i]){
      ans++;
    }
  }
  cout << ans << endl;
}

実装例 (Python)

N, Q = map(int, input().split())
W = list(map(int, input().split()))
S = [0] * (N+1)
for _ in range(Q):
  L, R, D = map(int, input().split())
  S[L-1] += D
  S[R] -= D

for i in range(N):
  S[i+1] += S[i]

ans = 0
for i in range(N):
  if S[i] >= W[i]:
    ans += 1

print(ans)

投稿日時:
最終更新: