問題
長さ $N$ の整数列 $A$ に対して以下を求めよ:
- 最長増加部分列(LIS)の長さ $L$
- 長さ $L$ の増加部分列の個数($\bmod 998244353$)
制約
$1 \le N \le 2 \times 10^5$
$1 \le A_i \le 10^9$
時間制限: 3sec / メモリ: 256MB
入出力例
入力例 1
6
3 1 4 1 5 9
出力例 1
4
2
入力例 2
5
1 3 2 4 5
出力例 2
4
3
概念図: (max, count) BIT の動作
ヒント(段階的開示)
ヒント1: 方向性
LIS の長さは patience sorting + 二分探索で O(N log N)。個数の数え上げも同様の DP で管理できる。各要素について「自分で終わる LIS の長さ」と「その個数」を求める。
ヒント2: (max, count) BIT
座標圧縮 + BIT で「A[j] < A[i] の中で dp[j] が最大のものの cnt 合計」を O(log N) で求める。BIT の各インデックスには (max_len, count) ペアを保持し、クエリ時に同 max_len なら count を合算。
ヒント3: 実装骨格
class BIT:
def query(self, i):
"""1..i の (max_len, count)"""
ml, mc = 0, 0
while i > 0:
if self.len_[i] > ml: ml, mc = self.len_[i], self.cnt[i]
elif self.len_[i] == ml: mc += self.cnt[i]
i -= i & (-i)
return ml, mc % MOD
def update(self, i, l, c):
while i <= self.n:
if l > self.len_[i]: self.len_[i], self.cnt[i] = l, c
elif l == self.len_[i]: self.cnt[i] = (self.cnt[i] + c) % MOD
i += i & (-i)
模範解答 (Python)
import sys
input = sys.stdin.readline
MOD = 998244353
class BIT:
def __init__(self, n):
self.n = n
self.len_ = [0] * (n+1)
self.cnt = [0] * (n+1)
def query(self, i):
ml, mc = 0, 0
while i > 0:
if self.len_[i] > ml:
ml = self.len_[i]
mc = self.cnt[i]
elif self.len_[i] == ml:
mc = (mc + self.cnt[i]) % MOD
i -= i & (-i)
return ml, mc
def update(self, i, l, c):
while i <= self.n:
if l > self.len_[i]:
self.len_[i] = l
self.cnt[i] = c
elif l == self.len_[i]:
self.cnt[i] = (self.cnt[i] + c) % MOD
i += i & (-i)
def solve():
N = int(input())
A = list(map(int, input().split()))
sorted_unique = sorted(set(A))
rank = {v: i+1 for i, v in enumerate(sorted_unique)}
M = len(sorted_unique)
bit = BIT(M)
dp = [0] * N
cnt = [0] * N
for i in range(N):
r = rank[A[i]]
ml, mc = bit.query(r - 1)
dp[i] = ml + 1
cnt[i] = mc if mc > 0 else 1
bit.update(r, dp[i], cnt[i])
L = max(dp)
total = 0
for i in range(N):
if dp[i] == L:
total = (total + cnt[i]) % MOD
print(L)
print(total)
solve()
Step-by-Step 解説
1問題の分解
LIS の長さ L は O(N log N) で求まる(patience sorting)。個数を求めるには各要素で終わる LIS の本数を DP で管理する。
LIS の長さ L は O(N log N) で求まる(patience sorting)。個数を求めるには各要素で終わる LIS の本数を DP で管理する。
2DP 定義
dp[i] = A[i] で終わる LIS の長さ。cnt[i] = そのような LIS の個数 mod p。遷移には「A[j] < A[i] かつ dp[j] が最大」の情報が必要。
3座標圧縮
A の値を 1-indexed のランクに変換。BIT のインデックスとして使用。
A の値を 1-indexed のランクに変換。BIT のインデックスとして使用。
4(max, count) BIT の実装
通常の BIT は加算ベースだが、(max_len, count) ペアを管理する特殊実装が必要。update 時:同じ長さなら count を加算、長い場合は置き換え。
通常の BIT は加算ベースだが、(max_len, count) ペアを管理する特殊実装が必要。update 時:同じ長さなら count を加算、長い場合は置き換え。
5最終集計
dp[i] == L となる全 cnt[i] の総和が答え。
dp[i] == L となる全 cnt[i] の総和が答え。
計算量
座標圧縮: $O(N \log N)$
各要素の BIT クエリ + 更新: $O(\log N)$ per element
全体: $O(N \log N)$
メモリ: $O(N)$
各要素の BIT クエリ + 更新: $O(\log N)$ per element
全体: $O(N \log N)$
メモリ: $O(N)$
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| cnt の初期値 | mc=0 のとき(最初の要素)は cnt=1 | cnt[i] = mc if mc > 0 else 1 |
| BIT の update で同長を加算しない | 同じ長さ達成の複数経路を見逃す | len_[i] == l のとき cnt += c |
| 座標圧縮で rank を 0-indexed にする | BIT が 1-indexed | rank = {v: i+1 for i, v in ...} |
| query(r) とすべきを query(r-1) にする | A[i] 自身を含めてしまう | bit.query(r - 1) で strictly less |
次のステップ
- 発展: 最長非増加部分列(LNDS)
- 応用: 2次元 LIS((x,y) 対で x も y も増加)= CDQ 分割統治
- 類題: LIS を $K$ 本非重複で選ぶ(Dilworth 定理 + フロー)