F - We're teapots Editorial by en_translator
Solving a subproblem
Consider the case where \(a_1 = \dots = a_{N-1} = -1\) and \(a_N = r \neq -1\). In other words, consider the following problem:
(Problem NOADJ) How many ways are there to choose \(r\) teapots out of \(N\) teapots arranged in a line, such that no two adjacent teapots are chosen?
We claim that the answer is equal to that of the following problem:
(Problem SIMPLE) How many ways are there to freely choose \(r\) out of \(N - (r-1)\) teapots?
To prove that, it suffices to say there is a one-to-one correspondence between the choice for Problem NOADJ and Problem SIMPLE.
Let \(i_1, \dots, i_r\) be the indices of teapots chosen in SIMPLE. Then, one can insert one teapot to each position to the left of teapots \(i_2, \dots, i_r\) to obtain any choice for NOADJ.
Conversely, let \(i_1, \dots, i_r\) be the indices of teapots chosen in NOADJ. Then, each of teapots \(i_2, \dots, i_r\) have an unchosen teapot to its left. Removing them yields any choice for SIMPLE.
Hence a bijection exists. The answer to Problem SIMPLE is \(\mathrm{binom}(N-(r-1),r)\) (where \(\mathrm{binom}\) is a binomial coefficient), so the answer to Problem NOADJ is also \(\mathrm{binom}(N-(r-1),r)\).
From now on, define \(\mathrm{noadj}(n, r) := \mathrm{binom}(n-(r-1), r)\).
Observations
For simplicity, we refer to teapots \(l, \dots, r\) as teapots \([l, r]\) collectively.
Let \(i_1, \dots, i_k\) be the indices \(i\) with \(a_i \neq -1\). For convenience, let \(a_0 = 0\) and \(i_0 = 0\). Then the constraints regarding \(a\) can be rephrased as follows:
- For \(j = 1, \dots, k\), exactly \((a_{i_j} - a_{i_{j-1}})\) teapots among teapots \([i_{j-1} + 1, i_j]\) have coffee.
- Any number of teapots among \([i_k + 1, N]\) can have coffee.
By rephrasing this way, every teapot is covered by exactly one segment-wise constraint.
When we know the number of ways to fill teapots \([1, i_{j-1}]\), how can we count the ways of filling teapots \([1, i_{j}]\)?
- Let \(x_0\) be the number of ways to fill teapots \([1, i_{j-1}]\), such that teapot \(i_{j-1}\) is not filled with coffee.
- Let \(x_1\) be the number of ways to fill teapots \([1, i_{j-1}]\), such that teapot \(i_{j-1}\) is filled with coffee.
- Let \(y_0\) be the number of ways to fill teapots \([1, i_{j}]\), such that teapot \(i_{j}\) is not filled with coffee.
- Let \(y_1\) be the number of ways to fill teapots \([1, i_{j}]\), such that teapot \(i_{j}\) is filled with coffee.
(For \(j=1\), define \(x_0=1\) and \(x_1=0\).) These satisfies:
\[\begin{bmatrix} x_0 & x_1 \\ \end{bmatrix} \begin{bmatrix} f_{0,0}(i_j - i_{j-1}, a_{i_j} - a_{i_{j-1}}) & f_{0,1}(i_j - i_{j-1}, a_{i_j} - a_{i_{j-1}}) \\ f_{1,0}(i_j - i_{j-1}, a_{i_j} - a_{i_{j-1}}) & f_{1,1}(i_j - i_{j-1}, a_{i_j} - a_{i_{j-1}}) \\ \end{bmatrix} = \begin{bmatrix} y_0 & y_1 \\ \end{bmatrix}.\]
Here, \(f_{s, t}(n, r)\) is the contribution from \(x_s\) to \(y_t\) when we fill exactly \(r\) out of \(n\) teapots with coffee, and can be written as
- \(f_{0,0}(n, r) = \mathrm{noadj}(n-1, r),\)
- \(f_{0,1}(n, r) = \begin{cases} \mathrm{noadj}(0, r-1) & (n=1) \\ \mathrm{noadj}(n-2, r-1) & (n \geq 2),\end{cases}\)
- \(f_{1,0}(n, r) = \begin{cases} \mathrm{noadj}(0, r) & (n=1) \\ \mathrm{noadj}(n-2, r) & (n \geq 2),\end{cases}\)
- \(f_{1,1}(n, r) = \begin{cases} 0 & (n=1) \\ \mathrm{noadj}(0, r-1) & (n=2) \\ \mathrm{noadj}(n-3, r-1) & (n \geq 3).\end{cases}\)
We denote them by \(\bm{f}(n, r)\).
Also, let us consider how we can count the ways to fill all the teapots based on the counts for teapots \([1, i_{k}]\). Let
- Let \(x_0\) be the number of ways to fill teapots \([1, i_{k}]\), such that teapot \(i_{k}\) is not filled with coffee.
- Let \(x_1\) be the number of ways to fill teapots \([1, i_{k}]\), such that teapot \(i_{k}\) is filled with coffee.
Then the sought count is \(x_0 \cdot fib[N - i_k] + x_1 \cdot fib[\max(N - i_k - 1, 0)]\), where \(fib[i]\) is obtained by the following recurrence relations:
\[fib[i] = \begin{cases} 1 & (i = 0) \\ 2 & (i = 1) \\ fib[i-1]+fib[i-2] & (i \geq 2). \end{cases}\]
Hence, if we can evaluate \(\bm{M} = \Pi_{j=1}^{k} \bm{f}(i_j - i_{j-1}, a_{i_j} - a_{i_{j-1}})\), the answer can be obtained as \(M_{0,0} \cdot fib[N - i_k] + M_{0,1} \cdot fib[\max(N - i_k - 1, 0)]\).
Per-query updates
Consider maintaining a sequence of matrices \(F = (F_1, \dots, F_N)\) such that:
- If \(a_i = -1\), let \(F_i\) contain the identity matrix \(\bm{E} = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}\);
- If \(a_i \neq -1\), let \(F_i\) contain \(\bm{f}(i - i', a_i - a_{i'})\), where \(i'\) is the maximum integer satisfying the following conditions. For convenience, if \(i'=0\), let \(a_{i'} = 0\).
- \(i' < i\).
- \(a_{i'} \neq -1\) or \(i' = 0\).
Then \(\prod_{i=1}^{N} F_i\) coincides with the matrix \(\bm{M}\) desired above.
When we want to update \(a_{x}\), we find the following values, then among the elements of \(F\), only those with \(i = x,\ x_{high}\) need to be updated:
- The maximum integer \(x_{low}\) such that:
- \(x_{low} < x\)
- \(a_{x_{low}} \neq -1\) or \(x_{low} = 0\)
- The maximum integer \(x_{high}\) such that:
- \(x_{high} > x\)
- \(a_{x_{high}} \neq -1\) or \(x_{high} = N+1\)
By using a balanced binary search tree or a segment tree, one can find \(x_{low}\) and \(x_{high}\) in \(O(\log N)\) time.
Also, updating an element of \(F\) and computing \(\prod_{i=1}^{N} F_i\) can be achieved with a segment tree in \(O(\log N)\) time each.
Hence, the problem has been solved with \(O(N)\)-time precomputation and \(O(\log N)\)-time process per query.
Sample code (C++)
In C++, one can use std::set
as an implementation of a balanced binary search tree, and atcoder::segtree
in AtCoder Library as a segment tree.
#include <iostream>
#include <cstdio>
#include <vector>
#include <array>
#include <set>
#include <map>
using std::cin;
using std::cout;
using std::cerr;
using std::endl;
using std::pair;
using std::make_pair;
using std::vector;
using std::min;
using std::max;
using std::array;
using std::set;
using std::map;
#include <atcoder/all>
using mint = atcoder::modint998244353;
using atcoder::segtree;
typedef long long int ll;
vector<mint> frac, invf;
void f_init (ll n) {
frac.resize(n+1);
invf.resize(n+1);
frac[0] = 1;
for (ll i = 1; i <= n; i++) {
frac[i] = frac[i-1] * i;
}
invf[n] = frac[n].inv();
for (ll i = n; i >= 1; i--) {
invf[i-1] = invf[i] * i;
}
}
mint ncr (ll n, ll r) {
if (n < r || n < 0 || r < 0) return 0;
if (frac.size() <= n) {
f_init(n+5);
}
return frac[n] * invf[n-r] * invf[r];
}
ll n, q;
using Mat = array<array<mint, 2>, 2>;
const Mat unity = {
(array<mint, 2>){1,0},
(array<mint, 2>){0,1}
};
Mat operator* (const Mat &l, const Mat &r) {
Mat x = {
(array<mint, 2>){0,0},
(array<mint, 2>){0,0}
};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
x[i][j] += l[i][k] * r[k][j];
}
}
}
return x;
}
Mat seg_op (Mat l, Mat r) {
return l * r;
}
Mat seg_e (void) {
return unity;
}
// n boxes, r balls into unique boxes, no adjacent boxes
mint noadj (ll n, ll r) {
if (n < r || r < 0) return 0;
return ncr((n + 1) - r, r);
}
Mat lenmat (ll n, ll r) {
Mat x;
x[0][0] = noadj(n-1, r);
if (n == 1) {
x[0][1] = noadj(0, r-1);
} else {
x[0][1] = noadj(n-2, r-1);
}
if (n == 1) {
x[1][0] = noadj(0, r);
} else {
x[1][0] = noadj(n-2, r);
}
if (n == 1) {
x[1][1] = 0;
} else if (n == 2) {
x[1][1] = noadj(0, r-1);
} else {
x[1][1] = noadj(n-3, r-1);
}
return x;
}
map<ll, ll> stidx; // set of i such that a_i != -1
void solve () {
segtree<Mat, seg_op, seg_e> seg(n);
vector<mint> fib(n+1);
fib[0] = 1;
fib[1] = 2;
for (ll i = 2; i <= n; i++) {
fib[i] = fib[i-2] + fib[i-1];
}
stidx.insert({-1, 0}); // bottom
stidx.insert({n, n}); // top
for (ll qi = 0; qi < q; qi++) {
ll x, y;
cin >> x >> y;
--x;
// erase
{
auto it = stidx.find(x);
if (it != stidx.end()) {
--it; ll ilow = (*it).first, vlow = (*it).second; ++it;
++it; ll ihigh = (*it).first, vhigh = (*it).second; --it;
stidx.erase(it);
seg.set(x, unity);
if (ihigh < n) seg.set(ihigh, lenmat(ihigh - ilow, vhigh - vlow));
}
}
// add
if (y != -1) {
stidx.insert({x, y});
auto it = stidx.find(x);
--it; ll ilow = (*it).first, vlow = (*it).second; ++it;
++it; ll ihigh = (*it).first, vhigh = (*it).second; --it;
seg.set(x, lenmat(x - ilow, y - vlow));
if (ihigh < n) seg.set(ihigh, lenmat(ihigh - x, vhigh - y));
}
// answer
Mat whole = seg.all_prod();
ll remain;
{
auto it = stidx.find(n);
--it; ll idx = (*it).first;
remain = (n-1) - idx;
}
mint ans = 0;
ans += whole[0][0] * fib[remain];
ans += whole[0][1] * fib[max(remain - 1, 0LL)];
cout << ans.val() << "\n";
}
return;
}
int main (void) {
std::ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> q;
solve();
return 0;
}
posted:
last update: