けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AOJ 3196 Increasing Sequence (AUPC 2020 day1-D)

in-place な DP にもっと慣れていきたい

問題へのリンク

問題概要

 N 個の数列がある。 i 個目の数列は  M_{i} 個の要素からなる。 i 個目の数列の  j 番目の要素は  A_{ij} と表す。

  •  i 個目の数列から 1 個以下の要素を選び、選んだ要素を元の数列の順番通りに並べた数列を  X とする。

数列  X が狭義単調増加となるように選ぶ方法は何通りあるか、1000000007 で割ったあまりを求めよ (数列が等しくても選び方が異なれば異なるものとする)。

制約

  •  1 \le N \le 10^{5}
  •  1 \le \sum_{j} M_{j} \le 10^{5}

考えたこと

とりあえず、登場する数値は高々  10^{5} 種類しかないようなので、座圧する。そのときの登場しうる数値の種類数を  S と表すことにする。

さて、この問題を見たとき、次のような DP をしたくなる

  • dp[ i ][ j ] := 最初の i 個の数列についての処理を確定したとき、数列の最後の値が j であるようにする操作の個数

このとき、DP は次のような式になる。

  • 各 j に対して dp[ i + 1 ][ j ] += dp[ i ][ j ] (なにも選ばない)
  • i 番目の数列中の値 k に対して、dp[ i + 1 ][ k ] += sum(dp[ i ][ j ], j = 0, 1, ..., k - 1)

このままでは  O(NS) の計算量となって間に合わない。しかしよく見ると、DP の更新を in-place にすれば 1 個目は無視できる。2 個目の更新については、BIT 上で DP すればそれぞれの値 k に対して  O(\log S) の計算量で更新できる。

全体の計算量は  O(S \log S) となる。

コード

#include <bits/stdc++.h>
using namespace std;
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

#define COUT(x) cout << #x << " = " << (x) << " (L" << __LINE__ << ")" << endl
template<class T1, class T2> ostream& operator << (ostream &s, pair<T1,T2> P)
{ return s << '<' << P.first << ", " << P.second << '>'; }
template<class T> ostream& operator << (ostream &s, vector<T> P)
{ for (int i = 0; i < P.size(); ++i) { if (i > 0) { s << " "; } s << P[i]; } return s; }
template<class T> ostream& operator << (ostream &s, vector<vector<T> > P)
{ for (int i = 0; i < P.size(); ++i) { s << endl << P[i]; } return s << endl; }
template<class T> ostream& operator << (ostream &s, set<T> P)
{ for(auto it : P) { s << "<" << it << "> "; } return s << endl; }
template<class T1, class T2> ostream& operator << (ostream &s, map<T1,T2> P)
{ for(auto it : P) { s << "<" << it.first << "->" << it.second << "> "; } return s << endl; }




// modint
template<int MOD> struct Fp {
    long long val;
    constexpr Fp(long long v = 0) noexcept : val(v % MOD) {
        if (val < 0) val += MOD;
    }
    constexpr int getmod() const { return MOD; }
    constexpr Fp operator - () const noexcept {
        return val ? MOD - val : 0;
    }
    constexpr Fp operator + (const Fp& r) const noexcept { return Fp(*this) += r; }
    constexpr Fp operator - (const Fp& r) const noexcept { return Fp(*this) -= r; }
    constexpr Fp operator * (const Fp& r) const noexcept { return Fp(*this) *= r; }
    constexpr Fp operator / (const Fp& r) const noexcept { return Fp(*this) /= r; }
    constexpr Fp& operator += (const Fp& r) noexcept {
        val += r.val;
        if (val >= MOD) val -= MOD;
        return *this;
    }
    constexpr Fp& operator -= (const Fp& r) noexcept {
        val -= r.val;
        if (val < 0) val += MOD;
        return *this;
    }
    constexpr Fp& operator *= (const Fp& r) noexcept {
        val = val * r.val % MOD;
        return *this;
    }
    constexpr Fp& operator /= (const Fp& r) noexcept {
        long long a = r.val, b = MOD, u = 1, v = 0;
        while (b) {
            long long t = a / b;
            a -= t * b, swap(a, b);
            u -= t * v, swap(u, v);
        }
        val = val * u % MOD;
        if (val < 0) val += MOD;
        return *this;
    }
    constexpr bool operator == (const Fp& r) const noexcept {
        return this->val == r.val;
    }
    constexpr bool operator != (const Fp& r) const noexcept {
        return this->val != r.val;
    }
    friend constexpr istream& operator >> (istream &is, Fp<MOD>& x) noexcept {
        is >> x.val;
        x.val %= MOD;
        if (x.val < 0) x.val += MOD;
        return is;
    }
    friend constexpr ostream& operator << (ostream &os, const Fp<MOD>& x) noexcept {
        return os << x.val;
    }
    friend constexpr Fp<MOD> modpow(const Fp<MOD> &a, long long n) noexcept {
        if (n == 0) return 1;
        auto t = modpow(a, n / 2);
        t = t * t;
        if (n & 1) t = t * a;
        return t;
    }
};

template <class Abel> struct BIT {
    Abel UNITY_SUM = 0;
    vector<Abel> dat;
    
    // [0, n)
    BIT(int n, Abel unity = 0) : UNITY_SUM(unity), dat(n, unity) { }
    void init(int n) {
        dat.assign(n, UNITY_SUM);
    }
    
    // a is 0-indexed
    inline void add(int a, Abel x) {
        for (int i = a; i < (int)dat.size(); i |= i + 1)
            dat[i] = dat[i] + x;
    }
    
    // [0, a), a is 0-indexed
    inline Abel sum(int a) {
        Abel res = UNITY_SUM;
        for (int i = a - 1; i >= 0; i = (i & (i + 1)) - 1)
            res = res + dat[i];
        return res;
    }
    
    // [a, b), a and b are 0-indexed
    inline Abel sum(int a, int b) {
        return sum(b) - sum(a);
    }
    
    // debug
    void print() {
        for (int i = 0; i < (int)dat.size(); ++i)
            cout << sum(i, i + 1) << ",";
        cout << endl;
    }
};

const int MOD = 1000000007;
using mint = Fp<MOD>;

int N;
vector<vector<int>> A;
vector<int> all;

mint solve() {
    sort(all.begin(), all.end());
    all.erase(unique(all.begin(), all.end()), all.end());
    int S = all.size();
    BIT<mint> bit(S + 10);
    bit.add(0, 1);
    for (int i = 0; i < N; ++i) {
        vector<pair<int,mint>> chs;
        for (int j = 0; j < A[i].size(); ++j) {
            int k = lower_bound(all.begin(), all.end(), A[i][j]) - all.begin() + 1;
            mint add = bit.sum(0, k);
            chs.emplace_back(k, add);
        }
        for (auto p : chs) bit.add(p.first, p.second);
    }
    return bit.sum(0, S+2);
}

int main() {
    while (cin >> N) {
        A.assign(N, vector<int>());
        all.clear();
        for (int i = 0; i < N; ++i) {
            int M;
            cin >> M;
            A[i].resize(M);
            for (int j = 0; j < M; ++j) cin >> A[i][j], all.push_back(A[i][j]);
        }
        cout << solve() << endl;
    }
}