2017 年の AGC の問題。現代なら ABC F で出そうだ。
問題概要
相異なる 個の整数
を 2 つのグループ X, Y に分けたい。
- グループ X では、どの 2 個の要素も差が
以下
- グループ Y では、どの 2 個の要素も差が
以下
となるようにする分け方は何通りあるか? 1000000007 で割った余りを求めよ。
制約
は小さい順に並んでいる
考えたこと
この手の「どちらに振り分けるか」という選択を繰り返すような問題は、DP で解けることが多い。
今回は、各グループにすでに入っている数と新たに入れる数との差が 以下であるかどうかを判定したいので、次のように DP を定義してみることにした。
dpX[i+1][j]:の最初の
個のみについて考える。それらを X, Y に振り分ける方法のうち、最後の要素
を X に入れる方法であって、Y に入っているものの最大値が
であるような方法の個数
dpY[i+1][j]:の最初の
個のみについて考える。それらを X, Y に振り分ける方法のうち、最後の要素
を Y に入れる方法であって、X に入っているものの最大値が
であるような方法の個数
DP の遷移を詰める
前回までの最後の要素が X 側で、新要素を X 側に入れるとき
のとき:
dpX[i+1][j] += dpX[i][j]- そうでないとき:
dpX[i+1][j] = 0([tex: j = 0, 1, \dots, i-2)
前回までの最後の要素が Y 側で、新要素を Y 側に入れるとき
のとき:
dpY[i+1][j] += dpY[i][j]- そうでないとき:
dpY[i+1][j] = 0([tex: j = 0, 1, \dots, i-2)
前回までの最後の要素が X 側で、新要素を Y 側に入れるとき
となる
について
dpY[i+1][i-1] += dpX[i][j]
前回までの最後の要素が Y 側で、新要素を X 側に入れるとき
となる
について
dpX[i+1][i-1] += dpY[i][j]
DP 高速化
上の DP は、in-place 化した上で、
- 区間 Change:区間 [l, r) の値をすべて x に更新する
- 区間和取得:区間 [l, r) の総和を求める
ができる遅延評価セグメント木を用いると、 の計算量で解ける。
......というのが、僕の AC した解法であった。
が、実は、区間 Change は必要なかった。僕は、in-place 化した配列 dp 上で dp[j] = 0 といった更新をする部分で、区間 Change をした。しかし、「最後に 0 にした箇所」を記録すれば、尺取法の要領で愚直に 0 にしていけばよいのだ。
コード
とはいえ、遅延評価セグメント木で殴るのは楽。ここでは遅延評価セグメント木で通したコードを。
#pragma GCC optimize("Ofast") #pragma GCC optimize("unroll-loops") #include <bits/stdc++.h> using namespace std; template<class S, class T> inline bool chmax(S &a, T b) { return (a < b ? a = b, 1 : 0); } template<class S, class T> inline bool chmin(S &a, T b) { return (a > b ? a = b, 1 : 0); } using pint = pair<int, int>; using pll = pair<long long, long long>; using tint = array<int, 3>; using tll = array<long long, 3>; using fint = array<int, 4>; using fll = array<long long, 4>; using qint = array<int, 5>; using qll = array<long long, 5>; using sint = array<int, 6>; using sll = array<long long, 6>; using vint = vector<int>; using vll = vector<long long>; using ll = long long; using u32 = unsigned int; using u64 = unsigned long long; using i128 = __int128_t; using u128 = __uint128_t; template <class T> using min_priority_queue = priority_queue<T, vector<T>, greater<T>>; #define REP(i, a) for (long long i = 0; i < (long long)(a); i++) #define REP2(i, a, b) for (long long i = a; i < (long long)(b); i++) #define RREP(i, a) for (long long i = (a)-1; i >= (long long)(0); --i) #define RREP2(i, a, b) for (long long i = (b)-1; i >= (long long)(a); --i) #define EB emplace_back #define PB push_back #define MP make_pair #define MT make_tuple #define FI first #define SE second #define ALL(x) x.begin(), x.end() #define COUT(x) cout << #x << " = " << (x) << " (L" << __LINE__ << ")" << endl //------------------------------// // Library //------------------------------// // mod inv template<class T_VAL, class T_MOD> constexpr T_VAL mod_inv(T_VAL a, T_MOD m) { T_VAL b = m, u = 1, v = 0; while (b > 0) { T_VAL t = a / b; a -= t * b, swap(a, b); u -= t * v, swap(u, v); } u %= m; if (u < 0) u += m; return u; } // modint template<int MOD = 998244353, bool PRIME = true> struct Fp { // inner value unsigned int val; // constructor constexpr Fp() : val(0) { } template<std::signed_integral T> constexpr Fp(T v) { long long tmp = (long long)(v % (long long)(get_umod())); if (tmp < 0) tmp += get_umod(); val = (unsigned int)(tmp); } template<std::unsigned_integral T> constexpr Fp(T v) { val = (unsigned int)(v % get_umod()); } constexpr long long get() const { return val; } constexpr static int get_mod() { return MOD; } constexpr static unsigned int get_umod() { return MOD; } // arithmetic operators constexpr Fp operator + () const { return Fp(*this); } constexpr Fp operator - () const { return Fp() - Fp(*this); } constexpr Fp operator + (const Fp &r) const { return Fp(*this) += r; } constexpr Fp operator - (const Fp &r) const { return Fp(*this) -= r; } constexpr Fp operator * (const Fp &r) const { return Fp(*this) *= r; } constexpr Fp operator / (const Fp &r) const { return Fp(*this) /= r; } constexpr Fp& operator += (const Fp &r) { val += r.val; if (val >= get_umod()) val -= get_umod(); return *this; } constexpr Fp& operator -= (const Fp &r) { val -= r.val; if (val >= get_umod()) val += get_umod(); return *this; } constexpr Fp& operator *= (const Fp &r) { unsigned long long tmp = val; tmp *= r.val; val = (unsigned int)(tmp % get_umod()); return *this; } constexpr Fp& operator /= (const Fp &r) { return *this = *this * r.inv(); } constexpr Fp pow(long long n) const { assert(n >= 0); Fp res(1), mul(*this); while (n) { if (n & 1) res *= mul; mul *= mul; n >>= 1; } return res; } constexpr Fp inv() const { if (PRIME) { assert(val); return pow(get_umod() - 2); } else { assert(val); return mod_inv((long long)(val), get_umod()); } } // other operators constexpr bool operator == (const Fp &r) const { return this->val == r.val; } constexpr bool operator != (const Fp &r) const { return this->val != r.val; } constexpr bool operator < (const Fp &r) const { return this->val < r.val; } constexpr bool operator > (const Fp &r) const { return this->val > r.val; } constexpr bool operator <= (const Fp &r) const { return this->val <= r.val; } constexpr bool operator >= (const Fp &r) const { return this->val >= r.val; } constexpr Fp& operator ++ () { ++val; if (val == get_umod()) val = 0; return *this; } constexpr Fp& operator -- () { if (val == 0) val = get_umod(); --val; return *this; } constexpr Fp operator ++ (int) { Fp res = *this; ++*this; return res; } constexpr Fp operator -- (int) { Fp res = *this; --*this; return res; } friend constexpr istream& operator >> (istream &is, Fp<MOD> &x) { long long tmp = 1; is >> tmp; tmp = tmp % (long long)(get_umod()); if (tmp < 0) tmp += get_umod(); x.val = (unsigned int)(tmp); return is; } friend constexpr ostream& operator << (ostream &os, const Fp<MOD> &x) { return os << x.val; } friend constexpr Fp<MOD> pow(const Fp<MOD> &r, long long n) { return r.pow(n); } friend constexpr Fp<MOD> inv(const Fp<MOD> &r) { return r.inv(); } }; // Lazy Segment Tree template<class Monoid, class Action> struct LazySegmentTree { // various function types using FuncMonoid = function<Monoid(Monoid, Monoid)>; using FuncAction = function<Monoid(Action, Monoid)>; using FuncComposition = function<Action(Action, Action)>; // core member int N; FuncMonoid OP; FuncAction ACT; FuncComposition COMP; Monoid IDENTITY_MONOID; Action IDENTITY_ACTION; // inner data int log, offset; vector<Monoid> dat; vector<Action> lazy; // constructor LazySegmentTree() {} LazySegmentTree(const FuncMonoid op, const FuncAction act, const FuncComposition comp, const Monoid &identity_monoid, const Action &identity_action) : OP(op), ACT(act), COMP(comp), IDENTITY_MONOID(identity_monoid), IDENTITY_ACTION(identity_action) {} LazySegmentTree(int n, const FuncMonoid op, const FuncAction act, const FuncComposition comp, const Monoid &identity_monoid, const Action &identity_action) { init(n, op, act, comp, identity_monoid, identity_action); } LazySegmentTree(const vector<Monoid> &v, const FuncMonoid op, const FuncAction act, const FuncComposition comp, const Monoid &identity_monoid, const Action &identity_action) { init(v, op, act, comp, identity_monoid, identity_action); } void init(const FuncMonoid op, const FuncAction act, const FuncComposition comp, const Monoid &identity_monoid, const Action &identity_action) { OP = op, ACT = act, COMP = comp; IDENTITY_MONOID = identity_monoid, IDENTITY_ACTION = identity_action; } void init(int n) { N = n, log = 0, offset = 1; while (offset < N) ++log, offset <<= 1; dat.assign(offset * 2, IDENTITY_MONOID); lazy.assign(offset * 2, IDENTITY_ACTION); } void init(const vector<Monoid> &v) { init((int)v.size()); build(v); } void init(int n, const FuncMonoid op, const FuncAction act, const FuncComposition comp, const Monoid &identity_monoid, const Action &identity_action) { init(op, act, comp, identity_monoid, identity_action); init(n); } void init(const vector<Monoid> &v, const FuncMonoid op, const FuncAction act, const FuncComposition comp, const Monoid &identity_monoid, const Action &identity_action) { init((int)v.size(), op, act, comp, identity_monoid, identity_action); build(v); } void build(const vector<Monoid> &v) { assert(N == (int)v.size()); for (int i = 0; i < N; ++i) dat[i + offset] = v[i]; for (int k = offset - 1; k > 0; --k) pull_dat(k); } int size() const { return N; } // basic functions for lazy segment tree void pull_dat(int k) { dat[k] = OP(dat[k * 2], dat[k * 2 + 1]); } void apply_lazy(int k, const Action &f) { dat[k] = ACT(f, dat[k]); if (k < offset) lazy[k] = COMP(f, lazy[k]); } void push_lazy(int k) { apply_lazy(k * 2, lazy[k]); apply_lazy(k * 2 + 1, lazy[k]); lazy[k] = IDENTITY_ACTION; } void pull_dat_deep(int k) { for (int h = 1; h <= log; ++h) pull_dat(k >> h); } void push_lazy_deep(int k) { for (int h = log; h >= 1; --h) push_lazy(k >> h); } // setter and getter, update A[i], i is 0-indexed, O(log N) void set(int i, const Monoid &v) { assert(0 <= i && i < N); int k = i + offset; push_lazy_deep(k); dat[k] = v; pull_dat_deep(k); } Monoid get(int i) { assert(0 <= i && i < N); int k = i + offset; push_lazy_deep(k); return dat[k]; } Monoid operator [] (int i) { return get(i); } // apply f for index i void apply(int i, const Action &f) { assert(0 <= i && i < N); int k = i + offset; push_lazy_deep(k); dat[k] = ACT(f, dat[k]); pull_dat_deep(k); } // apply f for interval [l, r) void apply(int l, int r, const Action &f) { assert(0 <= l && l <= r && r <= N); if (l == r) return; l += offset, r += offset; for (int h = log; h >= 1; --h) { if (((l >> h) << h) != l) push_lazy(l >> h); if (((r >> h) << h) != r) push_lazy((r - 1) >> h); } int original_l = l, original_r = r; for (; l < r; l >>= 1, r >>= 1) { if (l & 1) apply_lazy(l++, f); if (r & 1) apply_lazy(--r, f); } l = original_l, r = original_r; for (int h = 1; h <= log; ++h) { if (((l >> h) << h) != l) pull_dat(l >> h); if (((r >> h) << h) != r) pull_dat((r - 1) >> h); } } // get prod of interval [l, r) Monoid prod(int l, int r) { assert(0 <= l && l <= r && r <= N); if (l == r) return IDENTITY_MONOID; l += offset, r += offset; for (int h = log; h >= 1; --h) { if (((l >> h) << h) != l) push_lazy(l >> h); if (((r >> h) << h) != r) push_lazy(r >> h); } Monoid val_left = IDENTITY_MONOID, val_right = IDENTITY_MONOID; for (; l < r; l >>= 1, r >>= 1) { if (l & 1) val_left = OP(val_left, dat[l++]); if (r & 1) val_right = OP(dat[--r], val_right); } return OP(val_left, val_right); } Monoid all_prod() { return dat[1]; } // get max r that f(get(l, r)) = True (0-indexed), O(log N) // f(IDENTITY) need to be True int max_right(const function<bool(Monoid)> f, int l = 0) { if (l == N) return N; l += offset; push_lazy_deep(l); Monoid sum = IDENTITY_MONOID; do { while (l % 2 == 0) l >>= 1; if (!f(OP(sum, dat[l]))) { while (l < offset) { push_lazy(l); l = l * 2; if (f(OP(sum, dat[l]))) { sum = OP(sum, dat[l]); ++l; } } return l - offset; } sum = OP(sum, dat[l]); ++l; } while ((l & -l) != l); // stop if l = 2^e return N; } // get min l that f(get(l, r)) = True (0-indexed), O(log N) // f(IDENTITY) need to be True int min_left(const function<bool(Monoid)> f, int r = -1) { if (r == 0) return 0; if (r == -1) r = N; r += offset; push_lazy_deep(r - 1); Monoid sum = IDENTITY_MONOID; do { --r; while (r > 1 && (r % 2)) r >>= 1; if (!f(OP(dat[r], sum))) { while (r < offset) { push_lazy(r); r = r * 2 + 1; if (f(OP(dat[r], sum))) { sum = OP(dat[r], sum); --r; } } return r + 1 - offset; } sum = OP(dat[r], sum); } while ((r & -r) != r); return 0; } // debug stream friend ostream& operator << (ostream &s, LazySegmentTree seg) { for (int i = 0; i < (int)seg.size(); ++i) { s << seg[i]; if (i != (int)seg.size() - 1) s << " "; } return s; } // dump void dump() { for (int i = 0; i <= log; ++i) { for (int j = (1 << i); j < (1 << (i + 1)); ++j) { cout << "{" << dat[j] << "," << lazy[j] << "} "; } cout << endl; } } }; // range change range sum template<class Monoid> auto seg_op_add_with_sum = [](pair<Monoid, long long> p, pair<Monoid, long long> q) { return make_pair(p.first + q.first, p.second + q.second); }; template<class Monoid, class Action> auto seg_act_change_with_sum = [](Action f, pair<Monoid, long long> x) { return (f != Action(-1) ? make_pair(f * x.second, x.second) : x); }; template<class Action> auto seg_comp_change = [](Action g, Action f) { return (g != Action(-1) ? g : f); }; template<class Monoid, class Action> LazySegmentTree<pair<Monoid, long long>, Action> RangeChangeRangeSum(int N = 0) { return LazySegmentTree<pair<Monoid, long long>, Action>( N, seg_op_add_with_sum<Monoid>, seg_act_change_with_sum<Monoid, Action>, seg_comp_change<Action>, make_pair(Monoid(0), 1), Action(-1)); }; //------------------------------// // Solver //------------------------------// int main() { const ll MOD = 1000000007, INF = 1LL << 61; using mint = Fp<MOD>; ll N, A, B; cin >> N >> A >> B; deque<ll> S(N); REP(i, N) cin >> S[i]; S.push_front(-INF); N = S.size(); //vector<mint> dpA(N+1, 0), dpB(N+1, 0); // A 側ラストのものと、B 側ラストのもの auto dpA = RangeChangeRangeSum<mint, mint>(N+1), dpB = RangeChangeRangeSum<mint, mint>(N+1); dpA.apply(0, mint(1)), dpB.apply(0, mint(1)); REP2(i, 2, N) { // A ラストのものに B を入れる int it = upper_bound(ALL(S), S[i] - B) - S.begin(); chmin(it, i-1); dpB.apply(i-1, dpA.prod(0, it).first); // B ラストのものに A を入れる it = upper_bound(ALL(S), S[i] - A) - S.begin(); chmin(it, i-1); dpA.apply(i-1, dpB.prod(0, it).first); // A ラストのものにまた A を入れる if (S[i] - S[i-1] < A) dpA.apply(0, i-1, mint(0)); // B ラストのものにまた B を入れる if (S[i] - S[i-1] < B) dpB.apply(0, i-1, mint(0)); } mint res = dpA.all_prod().first + dpB.all_prod().first; cout << res << endl; }