# ARC169-C Not So Consecutive 2005diff https://atcoder.jp/contests/arc169/tasks/arc169_c ## 解法 動的計画法を考える。 DP[i][j][k] := ith/末尾の項がj/末尾の項がk項連続している ような列としてありえる数 遷移を考える。 $A_{i} = -1$なら$v = 1, 2, \dots, N$について、$A_{i} \ne -1$なら$v = A_{i}$について以下のように遷移する。 1. DP[i + 1][v] <- DP[i][v]とする 2. DP[i + 1][v]を右(?)に1シフトする 3. DP[i + 1][v][1] に DP[i]の総和 - DP[i][v]の総和 を代入する 答えはDP[N]の総和である。 これをそのまま愚直に計算すると$O(N^3)$かかる。 欲しいものがDP[N]の総和であること、DP[i + 1][v][1]の計算に全体の総和と行の総和しか利用しないことに注目する。DPテーブルを陽に持たなくても「現在のDPの総和」と「現在のDPのv行目の総和」を持っていればできるんじゃないかと疑う。 実際そうで、「右に1シフトする」を「$i + \text{hoge}$回目の遷移でDP[i][v][1]が行の総和から減算される」と言い換える。この$\text{hoge}$を毎回適切に計算してやれば良い。 全体$O(N^2)$で解けた。 ## 実装 ```cpp= #include <bits/stdc++.h> const long long MOD{998244353}; long long add(long long a, long long b) { a += b; if (a >= MOD) a -= MOD; return a; } long long inv(long long v) { return (v ? MOD - v : v); } long long sub(long long a, long long b) { return add(a, inv(b)); } int N; int A[5010]; bool check(const std::vector<int>& A) { std::vector<std::pair<int, int>> B; for (auto x : A) { if (B.empty() or B.back().first != x) { B.push_back(std::pair{ x, 1 }); } else { B.back().second++; } } for (auto [x, len] : B) { if (len >= x + 1) return false; } return true; } void brute() { std::vector<int> cur; long long ans{}; auto dfs{[&](auto dfs, int i) -> void { if (i == (int)N) { ans = add(ans, (int)check(cur)); } else { if (A[i] == -1) { for (int j{1} ; j <= N ; j++) { cur.push_back(j); dfs(dfs, i + 1); cur.pop_back(); } } else { cur.push_back(A[i]); dfs(dfs, i + 1); cur.pop_back(); } } }}; dfs(dfs, 0); std::cout << ans << '\n'; } int main() { std::cin.tie(nullptr)->sync_with_stdio(false); std::cin >> N; for (int i{} ; i < N ; i++) { std::cin >> A[i]; } long long sumAll{}; std::vector<long long> rowSum(N + 1); std::vector reserveSub(N + 1, std::vector<long long>(N + 1)); auto reserve{[&](int i, int v, long long val) -> void { // std::cout << i << ',' << v << " de " << val << " wo hiku" << std::endl; reserveSub[i][v] = add(reserveSub[i][v], val); }}; // A_1の分を計算 // std::cout << "calc A[1]" << std::endl; { int j{}; while (j < N and (A[j] == -1 or A[0] == A[j])) j++; int k{j}; while (k < N and (A[k] == -1 or A[j] == A[k])) k++; for (int v{1} ; v <= N ; v++) if (A[0] == v or A[0] == -1) { sumAll = add(sumAll, 1); rowSum[v] = add(rowSum[v], 1); int idx{std::min(A[0] == -1 and j < N and A[j] == v ? k : j, v)}; reserve(idx, v, 1); } } for (int i{1} ; i < N ; i++) { // std::cout << "calc A[" << i + 1 << "]\n"; std::vector<long long> cur(N + 1); for (int v{1} ; v <= N ; v++) if (A[i] == v or A[i] == -1) { cur[v] = sub(sumAll, rowSum[v]); } for (int v{1} ; v <= N ; v++) { long long kiyo{sub(cur[v], reserveSub[i][v])}; sumAll = add(sumAll, kiyo); rowSum[v] = add(rowSum[v], kiyo); } int j{i}; while (j < N and (A[j] == -1 or A[j] == A[i])) j++; int k{j}; while (k < N and (A[k] == -1 or A[j] == A[k])) k++; for (int v{1} ; v <= N ; v++) { int idx{std::min(A[i] == -1 and j < N and v == A[j] ? k : j, i + v)}; reserve(idx, v, cur[v]); } } std::cout << sumAll << '\n'; // brute(); } ``` [提出](https://atcoder.jp/contests/arc169/submissions/58280220) ## 感想 「簡単に思いつく愚直から$N$を一個落とす」問題は得意だと自負しているが、それでも1時間以上かかってしまった。 シフトの言い換えが大変だった。