ある角マス $i$ に対して $0~i-1$ の角マスのDPが分かっている場合、
角マス間の遷移はカタラン数の偶数番目 $c_2,c_4,c_6,...$ を、$DP[i-1],DP[i-2],...$ と掛け合わせたものの合計となる。
「遷移元となるのは、値が確定してもう更新されないDP」(そうしないと、遷移元が更新されるたびに遷移先も更新が発生する)という点に注意すると、
角マスの $\mathrm{DP}_1[i]$ が確定するのは、分割統治FFTで $[i,i+1)$ の処理に来たときである。
そのタイミングで、壁マスに関する遷移を計算する。
import os
import sys
import numba
import numpy as np
MOD = 998244353
SUM_E = np.array(
[911660635, 509520358, 369330050, 332049552, 983190778, 123842337, 238493703, 975955924, 603855026, 856644456,
131300601, 842657263, 730768835, 942482514, 806263778, 151565301, 510815449, 503497456, 743006876, 741047443,
56250497, 867605899, 0, 0, 0, 0, 0, 0, 0, 0], np.int64)
SUM_IE = np.array(
[86583718, 372528824, 373294451, 645684063, 112220581, 692852209, 155456985, 797128860, 90816748, 860285882,
927414960, 354738543, 109331171, 293255632, 535113200, 308540755, 121186627, 608385704, 438932459, 359477183,
824071951, 103369235, 0, 0, 0, 0, 0, 0, 0, 0], np.int64)
def convolution(aaa, bbb):
def bit_length(n):
x = 0
while 1 << x < n:
x += 1
return x
def bit_scan_forward(n):
x = 0
while n & 1 == 0:
n >>= 1
x += 1
return x
def mod_pow(x, a, MOD):
ret = 1
cur = x
while a > 0:
if a & 1:
ret = ret * cur % MOD
cur = cur * cur % MOD
a >>= 1
return ret
def butterfly(aaa, mod, sum_e):
n = aaa.size
h = bit_length(n)
for ph in range(1, h + 1):
w = 1 << (ph - 1)
p = 1 << (h - ph)
now = 1
for s in range(w):
offset = s << (h - ph + 1)
for i in range(p):
l = aaa[i + offset]
r = aaa[i + offset + p] * now % mod
aaa[i + offset] = (l + r) % mod
aaa[i + offset + p] = (l - r) % mod
now = now * sum_e[bit_scan_forward(~s)] % mod
def butterfly_inv(aaa, mod, sum_ie):
n = aaa.size
h = bit_length(n)
for ph in range(h, 0, -1):
w = 1 << (ph - 1)
p = 1 << (h - ph)
inow = 1
for s in range(w):
offset = s << (h - ph + 1)
for i in range(p):
l = aaa[i + offset]
r = aaa[i + offset + p]
aaa[i + offset] = (l + r) % mod
aaa[i + offset + p] = ((l - r) * inow) % mod
inow = inow * sum_ie[bit_scan_forward(~s)] % mod
n = aaa.size
m = bbb.size
k = n + m - 1
z = 1 << bit_length(k)
raaa = np.zeros(z, np.int64)
rbbb = np.zeros(z, np.int64)
raaa[:n] = aaa
rbbb[:m] = bbb
butterfly(raaa, MOD, SUM_E)
butterfly(rbbb, MOD, SUM_E)
for i in range(z):
raaa[i] = raaa[i] * rbbb[i] % MOD
butterfly_inv(raaa, MOD, SUM_IE)
iz = mod_pow(z, MOD - 2, MOD)
for i in range(k):
raaa[i] = raaa[i] * iz % MOD
return raaa[:k]
SIGNATURE = '(i8[:],i8[:])'
if sys.argv[-1] == 'ONLINE_JUDGE':
from numba.pycc import CC
cc = CC('my_module')
cc.export('convolution', SIGNATURE)(convolution)
cc.compile()
exit()
if os.name == 'posix':
# noinspection PyUnresolvedReferences
from my_module import convolution
else:
from numba import njit
convolution = njit(SIGNATURE, cache=True)(convolution)
print('compiled', file=sys.stderr)
def precompute_factorials(n, MOD):
f = 1
factorials = [1]
for m in range(1, n + 1):
f = f * m % MOD
factorials.append(f)
f = pow(f, MOD - 2, MOD)
finvs = [1] * (n + 1)
finvs[n] = f
for m in range(n, 1, -1):
f = f * m % MOD
finvs[m - 1] = f
return factorials, finvs
input = sys.stdin.buffer.readline
n, m = map(int, input().split())
facts, finvs = precompute_factorials(n * 2, MOD)
n2 = n // 2
# 偶数番目のカタラン数リスト
catalan_even = [facts[i * 2] * finvs[i] * finvs[i + 1] % MOD for i in range(0, n + 1, 2)]
catalan_even = np.array(catalan_even, dtype=np.int64)
dp1 = np.zeros(n2 + 1, dtype=np.int64)
dp2 = [0] * m
dp1[0] = 1
walls = []
for wi in range(m):
i, j = map(int, input().split())
i += 1
j -= 1
walls.append((j, i))
walls.sort()
wall_index = []
wi = 0
for i in range(n2 + 1):
while wi < m and walls[wi][0] // 2 < i:
wi += 1
wall_index.append(wi)
wall_index.append(m)
def get_path_count(si, sj, ti, tj):
# i=j のナナメライン上にあるマスを通らない経路数
di1 = ti - si
dj1 = tj - sj
di2 = tj - si
dj2 = ti - sj
assert di1 >= 0 and dj1 >= 0
res = facts[di1 + dj1] * finvs[di1] * finvs[dj1]
if di2 >= 0 and dj2 >= 0:
res -= facts[di2 + dj2] * finvs[di2] % MOD * finvs[dj2]
res %= MOD
return res
def divide_and_conquer(l, r):
if l + 1 == r:
# 角マス dp1[l] が確定したので、lより右下の壁マスの dp2 に遷移
xi = l * 2 + 1
xj = l * 2
dp1l = dp1[l]
for wi in range(wall_index[l], m):
j, i = walls[wi]
dp2[wi] -= dp1l * get_path_count(xi, xj, i, j)
dp2[wi] %= MOD
# l の下の壁マスについて、角マス→壁マスの遷移は確定したので、壁→壁 の遷移を計算
for wi1 in range(wall_index[l], wall_index[l + 1]):
j1, i1 = walls[wi1]
dp2w = dp2[wi1]
for wi2 in range(wi1 + 1, m):
j2, i2 = walls[wi2]
if i1 <= i2:
dp2[wi2] -= dp2w * get_path_count(i1, j1, i2, j2)
dp2[wi2] %= MOD
# l の下の壁マスの dp2 が確定したので、壁マスより右下にある角マス dp1 に遷移
for ci in range(i1 // 2, n2 + 1):
xi = ci * 2 + 1
xj = ci * 2
dp1[ci] -= dp2w * get_path_count(i1, j1, xi, xj)
dp1[i1 // 2:] %= MOD
return
mid = (l + r) // 2
divide_and_conquer(l, mid)
left = dp1[l:mid]
right = catalan_even[:r - l]
cnv = convolution(left, right)
dp1[mid:r] -= cnv[mid - l:r - l]
dp1[mid:r] %= MOD
divide_and_conquer(mid, r)
divide_and_conquer(0, n2 + 1)
ans = -dp1[n2] % MOD
print(ans)