새소식

PS/백준

[백준/22976] 계산 최적화 (Python)

  • -

Problem : https://www.acmicpc.net/problem/22976

 

22976번: 계산 최적화

현욱은 덧셈과 곱셈을 계산할 수 있는 간단한 계산 장치를 만들었다. 이 계산 장치는 처음에 값 $0$에서 시작해서 $N$($1 \le N \le 5 \cdot 10^5 $)번의 연산을 순서대로 수행한 결과값을 반환한다. 연산

www.acmicpc.net

 

Difficulty : Platinum 3

 

Status : Solved

 

Time : 00:43:50

 


 

문제 설명

 

더보기

현욱은 덧셈과 곱셈을 계산할 수 있는 간단한 계산 장치를 만들었다. 이 계산 장치는 처음에 값 0에서 시작해서 N(1 <= N <= 5 * 10^5 )번의 연산을 순서대로 수행한 결과값을 반환한다. 연산은 아래의 두 종류가 있다.

 

  • + k : 이전 값에 k를 더한다(1 <= k <= 10^9)
  • * k : 이전 값에 k를 곱한다 (1 <= k <= 10^9)

연산 결과값이 아주 커질 수 있기 때문에 이 계산 장치는 항상 계산 결과값을 10^9+7로 나눈 나머지를 저장한다.
현욱은 이 계산 장치에서 일부 연산을 변경한 후 계산 결과가 어떻게 달라지는지를 확인하고 싶다. 현욱은 총 Q(1 <= Q <= 5 * 10^5 ) 번 계산 장치의 연산을 변경할 예정이며, 연산 변경은 다음과 같은 형태로 주어진다.

 

  • i + k : i번째 연산을 이전 값에 k를 더하는 연산으로 변경한다 (1 <= i <= N, 1 <= k <= 10^9)
  • i * k : i번째 연산을 이전 값에 k를 곱하는 연산으로 변경한다 (1 <= i <= N, 1 <= k <= 10^9)

모든 변경 사항은 누적된다.

현욱은 연산 변경이 하나 일어날 때마다 다시 처음부터 계산을 하는게 너무 비효율적이라고 생각한다. 현욱을 도와 현욱의 계산 장치를 최적화해서, 연산 변경이 일어날 때 결과를 빠르게 다시 계산하는 프로그램을 작성해보자. 

 

 

입력 및 출력

 

더보기

입력

 

첫줄에 연산의 개수 N과 연산을 변경할 횟수 Q가 공백으로 구분되어 주어진다(1 <= N, Q <= 5 * 10^5 ).

둘째 줄부터 N줄에 걸쳐 맨 처음 계산 장치를 구성하는 N개의 연산이 순서대로 주어진다.

그 다음부터 Q줄에 걸쳐 계산 장치의 연산을 어떻게 변경했는지 정보가 순서대로 주어진다.

 

출력

 

Q줄에 걸쳐 연산이 바뀔 때마다 계산 장치의 계산 결과값이 어떻게 바뀌는지 출력한다.

 

입력 예시

 

6 10
+ 3
* 2
+ 2
* 3
+ 4
* 5
1 + 2
3 * 1
4 * 5
2 + 6
2 * 2
1 * 2
3 * 1
4 + 4
1 + 5
3 * 4

 

출력 예시

 

110
80
120
220
120
20
20
40
90
240

 

 


 

풀이

 

이 문제에서 영감을 받았다.

 

2024.01.17 - [PS/백준] - [백준/13925] 수열과 쿼리 13 (Python)

 

[백준/13925] 수열과 쿼리 13 (Python)

Problem : https://www.acmicpc.net/problem/13925 13925번: 수열과 쿼리 13 길이가 N인 수열 A1, A2, ..., AN이 주어진다. 이때, 다음 쿼리를 수행하는 프로그램을 작성하시오. 1 x y v: Ai = (Ai + v) % MOD를 수행한다. (x ≤

magentino.tistory.com

 

이 문제에서 못다한 말을 하자면, 사칙연산은 일차식 함수의 꼴로 구성할 수 있고, 일차식 함수의 합성함수 역시 일차식이다.

 

f(x) = ax + b이고, 이를 [a, b]꼴로 저장한다고 가정하자.

  • 리프 노드에서의 f(x)는 다음과 같다.
    • + k : [1, k]
    • * k : [k, 0]
    • 업데이트 시 리프 노드를 위 방식과 같이 업데이트할 수 있다.
  • 또한, 리프 노드가 아닌 노드에서를 고려하자.
    • 왼쪽 노드의 함수를 f(x), 오른쪽 노드의 함수를 g(x) = cx + d라고 두자. 그렇다면 이 노드의 함수 h(x) = g(f(x))가 될 것이다.
    • 그렇다면 f(x) = ax + b이므로 h(x) = c(ax + b) + d = acx + bc + d 로 풀이할 수 있으며, 이 역시 일차식이다.
    • h(x)를 앞선 경우와 같이 저장하면 [ac, bc + d]가 된다.
  • 즉, 우리는 연산 업데이트를 O(logN)의 시간복잡도로 수행할 수 있다. 그 결과는 세그먼트 트리의 루트 노드에 저장되며, 초깃값은 0이므로 우리가 찾는 값은 root[0] * 0 + root[1] = root[1]이 된다.

 

 

풀이 코드

import sys
input = sys.stdin.readline
MOD = 10**9 + 7

N, Q = map(int, input().split())
ops = [input().split() for _ in range(N)]
index = [0]*N
tree = [[0]*2 for i in range(4*N)]

def init(start=0, end=N-1, idx=1) :
  if start == end :
    op, num = ops[start]
    if op == '+' :
      tree[idx] = [1, int(num)]
    else :
      tree[idx] = [int(num), 0]
    index[start] = idx
    return
  mid = (start + end) // 2
  init(start, mid, idx*2)
  init(mid+1, end, idx*2+1)
  tree[idx][0] = (tree[idx*2][0] * tree[idx*2+1][0]) % MOD
  tree[idx][1] = (tree[idx*2][1] * tree[idx*2+1][0] + tree[idx*2+1][1]) % MOD

def update(idx, op, num) :
  idx = index[idx]
  if op == '+' :
    tree[idx] = [1, num]
  else :
    tree[idx] = [num, 0]
  while idx > 1 :
    idx //= 2
    tree[idx][0] = (tree[idx*2][0] * tree[idx*2+1][0]) % MOD
    tree[idx][1] = (tree[idx*2][1] * tree[idx*2+1][0] + tree[idx*2+1][1]) % MOD
    

init()
for _ in range(Q) :
  idx, op, num = input().split()
  update(int(idx)-1, op, int(num))
  print(tree[1][1])

풀이 완료!

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.