https://www.acmicpc.net/problem/21268
솔루션에서 Tellegen's Principle을 인용하고 있어서 지난 포스트와 연관해 작성해본다. 머리 좋은 사람들은 아마 생각하지 않고도 풀 수 있을 것 같다.
Without Tellegen's principle
Ek=∑i=1nCi∏j=1k(Ai+Bj)를 답이라고 하자.
i와 관련한 항들을 사부작거리다보면, Pj=∑i=1nCiAij를 미리 계산해두면 편할 것 같다.
∑jFk,jxj=(x+B1)(x+B2)⋯(x+Bk)라고 두면, Ek=∑jPjFk,j가 된다는 사실을 알 수 있다. 만약 Fk,k−j꼴이었다면 전형적인 다항식 곱하기인데 미묘하게 다르다. 그럴때는 x←x−1을 대입해주고 xk를 곱해서 Gk(x)=(B1x+1)⋯(Bkx+1)을 생각해주자.
그러면 P(x)=∑jPjxj를 생각했을 때 Ek=[xk]P(x)Gk(x)가 된다. 일단 G1(x),⋯,Gn(x)를 다 곱해볼 수는 당연히 없고, 각 k마다 xk의 계수만 필요하다는 것에 주목해 분할 정복을 생각해보자.
구간 [s,e]에 대해 Es,⋯,Ee를 구해야 한다고 생각해보자. P(x)Gs−1(x)를 미리 알고 있다고 가정하고, [s,m]에 대한 oracle을 호출해 Es,⋯,Em까지는 알고 있다고 가정한다. 이제 P(x)Gs−1(x)에 (Bsx+1)⋯(Bmx+1)을 곱해서 P(x)Gm(x)를 만들고 [m+1,e]로 분할정복을 하고 싶다.
문제는 P(x)의 차수가 n까지 커질 수 있으니 분할 정복이 안 된다. 연산량이 적어도 e−s에 대한 함수로 나와야 할 텐데..
그런데 잘 생각해보면 i∈[m+1,e]에 대해 P(x)Gi(x)의 i차항은 (Bm+1x+1)⋯(Bix+1)이 i−m차식임을 고려할 때 P(x)Gm(x)의 m차항 이상 e차항 이하만 확인하면 된다는 사실을 알 수 있다.
그렇다면 P(x)Gs−1(x)를 다 들고 다니는 대신, P(x)Gs−1(x)의 [s,e]차항만 들고 다니자. 이는 e−s+1차식으로 충분히 감당 가능하다. [s,m] 구간을 분할정복하고, m−s차식인 (Bsx+1)⋯(Bmx+1)을 곱하고, [m+1,e]차항만 뽑아내서 [m+1,e] 구간을 분할정복해주면 된다. O((e−s)log(e−s)) 정도의 연산이 필요하니 전체 시간 복잡도는 O(Nlog2N)이다.
P(x)를 어떻게 계산하는지 설명 안 했는데, 결국 P(x)=∑i=1n1−AixCi꼴이다. Ci=1인 경우에는 적분식이 log∏i=1n(1−Aix) 꼴인 걸 이용해서 곱하고 로그취하고 미분하는 방식이 있는데, 사실 그냥 더 간단하게 분할 정복으로 분자, 분모를 유지하면서 더해주면 역시 O(Nlog2N)이면 된다.
Ek=∑jPjFk,j 에서, Fk,j를 행렬로 보는 것이 자연스럽다. 그렇다면 이 transpose는 어떨까?
벡터 a1,⋯,aN이 주어졌을 때 bj=∑kakFk,j를 구한다고 생각해보면, ∑jbjxj=∑kak(x+B1)⋯(x+Bk)가 되는 걸 알 수 있다. 저 다항식 곱 계산은 분할 정복으로 쉽게 할 수 있다.
Tellegen's principle을 적용하여 해당 알고리즘의 transpose를 구해보자. 가장 자연스러운 분할 정복 알고리즘은 다음과 같다.
def solve([s, e], a) -> polynomial[x]:
if s == e:
return a[s] # constant polynomial
m = (s + e) / 2
p = solve([s, m], a)
B = (x + B_s) ... (x + B_m) # acquired from another DnC
q = solve([m+1, e], a)
return p + B * q
즉 solve(s, e, a)
라는 연산자는 e−s+1개의 수 as,⋯,ae를 입력으로 받아 똑같이 ∑jβjxj=p+Bq를 만족하는 βs,⋯,βe로 돌려주는 linear operator로 생각할 수 있다. 딱딱하게 쓰면,
solve[s,e]=solve[s,m]+multiplyB∘solve[m+1,e]
이 transpose는 다음과 같다.
solvet[s,e]=solvet[s,m]+solvet[m+1,e]∘multiplyBt
지난 포스트에서 multiply의 transpose가 middle product라고 썼는데, 나한테는 이것저것 다 헷갈려서 그냥 B(x−1)을 곱하고 non-negative 차수만 남긴 거라고 외운다. 이 연산이 어떻게 생겼는지 구현하다보면 위의 풀이와 완전히 동일한 결론을 얻는다.
여담으로 P(x)를 계산하는 과정 역시 dual이 있는데, Ci가 주어졌을 때 P(x)를 계산하는 것을 generalized power sum이라고 하고, 그 transpose는 multiple point evaluation이 된다.