• home
  • about
  • 全ての投稿
  • ソフトウェア・ハードウェアの設定のまとめ
  • 分析関連のまとめ
  • ヘルスケア関連のまとめ
  • 生涯学習関連のまとめ

二次元の累積和

date: 2021-05-31 excerpt: 二次元の累積和について

tag: algorithmdynamic programmingcumsum累積和


二次元累積和(cumsum-2d)について

概要

  • 二次元で累積和を計算する時、最初に一番上段と、一番左の累積を計算し、それ以外の部分についてもらうdpのイメージで計算することで任意の点の累積和を求めることができる
  • 任意の区間での面積を求めるには、いもす法的な発想を用いる

典型例; 値が入っているmatrixが与えられた時、任意の区間での値の和はいくらか

  • 概要
    • 最初に累積和を計算した変数を持っておき、いもす法ではみ出した部分を引くことで、任意の区間について計算できる
  • 問題
    • 304. Range Sum Query 2D - Immutable/LeetCode
from typing import *
import copy
class NumMatrix:
    def __init__(self, matrix: List[List[int]]):
        m = self.m = copy.deepcopy(matrix)
        H, W = len(m), len(m[0])
        for h in range(1, H):
            m[h][0] += m[h-1][0]
        for w in range(1, W):
            m[0][w] += m[0][w-1]
        for h in range(1, H):
            for w in range(1, W):
                m[h][w] += m[h-1][w] + m[h][w-1] - m[h-1][w-1]
        print(*m, sep="\n")

    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        h1, w1, h2, w2 = row1, col1, row2, col2
        min_h, max_w = min(h1, h2), max(w1, w2)
        max_h, min_w = max(h1, h2), min(w1, w2)
        tmp = self.m[max_h][max_w]
        if min_h > 0:
            tmp -= self.m[min_h-1][max_w]
        if min_w > 0:
            tmp -= self.m[max_h][min_w-1]
        if min_h > 0 and min_w > 0:
            tmp += self.m[min_h-1][min_w-1]
        return tmp

nm = NumMatrix([[3, 0, 1, 4, 2], [5, 6, 3, 2, 1], [1, 2, 0, 1, 5], [4, 1, 0, 1, 7], [1, 0, 3, 0, 5]])
print(nm.sumRegion(2, 1, 4, 3)) # 8
print(nm.sumRegion(1, 1, 2, 2)) # 11
print(nm.sumRegion(1, 2, 2, 4)) # 12

pythonのテンプレート

テンプレート1

  • 一部の問題でテンプレート1の方法がそのまま通用しなかったり、コーナーケースで失敗するので注意して利用する必要がある
class Cumsum2d:
    dp: "Optional[List[List]]" = None
    @staticmethod
    def generate(h, w, a):
        '''da[i][j]:(0,0)~(i,j)の長方形の和'''
        Cumsum2d.dp = dp = [[0]*w for j in range(h)]
        dp[0][0] = a[0][0]
        for i in range(1,w):
            dp[0][i] = dp[0][i-1]+a[0][i]
        for i in range(1,h):
            cnt_w = 0
            for j in range(w):
                cnt_w += a[i][j]
                dp[i][j] = dp[i-1][j]+cnt_w
        return dp
    @staticmethod
    def calc(p, q, x, y):
        dp = Cumsum2d.dp
        '''da_calc(p,q,x,y):(p,q)~(x,y)の長方形の和'''
        if p > x or q > y:
            return 0
        if p == 0 and q == 0:
            return dp[x][y]
        if p == 0:
            return dp[x][y]-dp[x][q-1]
        if q == 0:
            return dp[x][y]-dp[p-1][y]
        return dp[x][y]-dp[p-1][y]-dp[x][q-1]+dp[p-1][q-1]

テンプレート2

  • すこし入力より大きいマトリックスを作成して直接累積和を計算する
G = [[0]*(W+1) for _ in range(H+1)]
for h, w in zip(A, B):
    G[h+1][w+1] += 1

for h in range(1, H+1):
    for w in range(1, W+1):
        G[h][w] += G[h-1][w]
for h in range(1, H+1):
    for w in range(1, W+1):
        G[h][w] += G[h][w-1]

例; ある範囲以下の最大を求めるような問題の場合、二次元累積和が使用できる

問題

  • 競プロ典型 90 問; 081 - Friendly Group

解説

  • 面積や大きさを求める問題でないので直感的に結びつかないが、範囲内の最大値を求めるという文脈でいもす法(二次元累積和)が使用できる

解答

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

A, B = [], []
for n in range(N):
    a,b = map(int, input().split())
    A.append(a); B.append(b)

H = max(A + [K]) + 1
W = max(B + [K]) + 1
G = [[0]*(W+1) for _ in range(H+1)]
for h, w in zip(A, B):
    G[h+1][w+1] += 1

for h in range(1, H+1):
    for w in range(1, W+1):
        G[h][w] += G[h-1][w]
for h in range(1, H+1):
    for w in range(1, W+1):
        G[h][w] += G[h][w-1]

ans = 0
for h in range(H-K):
    for w in range(W-K):
        tmp = G[h][w] + G[h+K+1][w+K+1] - G[h][w+K+1] - G[h+K+1][w]
        ans = max(ans, tmp)
print(ans)

例; チョコレートの切り方を総当りで見つける例

問題
AtCoder Regular Contest 025; B - チョコレート

解答

class Cumsum2d:
    dp: "Optional[List[List]]" = None
    @staticmethod
    def generate(h, w, a):
        '''da[i][j]:(0,0)~(i,j)の長方形の和'''
        Cumsum2d.dp = dp = [[0]*w for j in range(h)]
        dp[0][0] = a[0][0]
        for i in range(1,w):
            dp[0][i] = dp[0][i-1]+a[0][i]
        for i in range(1,h):
            cnt_w = 0
            for j in range(w):
                cnt_w += a[i][j]
                dp[i][j] = dp[i-1][j]+cnt_w
        return dp
    @staticmethod
    def calc(p, q, x, y):
        dp = Cumsum2d.dp
        '''da_calc(p,q,x,y):(p,q)~(x,y)の長方形の和'''
        if p > x or q > y:
            return 0
        if p == 0 and q == 0:
            return dp[x][y]
        if p == 0:
            return dp[x][y]-dp[x][q-1]
        if q == 0:
            return dp[x][y]-dp[p-1][y]
        return dp[x][y]-dp[p-1][y]-dp[x][q-1]+dp[p-1][q-1]

import itertools

H,W=map(int, input().split())

C = [list(map(int, input().split())) for _ in range(H)]
for h,w in itertools.product(range(H), range(W)):
        if (h+w)%2==0: # ブラックチョコならば
            C[h][w] *= -1 # 正負を反転

Cumsum2d.generate(H,W,C)
ans = 0
for p, q in itertools.product(range(H), range(W)):
    for x, y in itertools.product(range(p, H), range(q, W)):
        if Cumsum2d.calc(p,q,x,y) == 0:
            ans = max(ans, (x-p+1)*(y-q+1))
print(ans)


algorithmdynamic programmingcumsum累積和 Share Tweet