---
# System prepended metadata

title: ARC169-C Not So Consecutive
tags: [AtCoder]

---

# 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時間以上かかってしまった。

シフトの言い換えが大変だった。