Skip to content

Commit b532f69

Browse files
authored
Merge pull request #403 from hitonanode/north-east-lattice-paths
north-east lattice paths
2 parents 0ba9ba0 + e94f79c commit b532f69

File tree

6 files changed

+545
-1
lines changed

6 files changed

+545
-1
lines changed

modint.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,14 @@ template <int md> struct ModInt {
138138
return ModInt::fac(n) * ModInt::facinv(n - r);
139139
}
140140

141-
static ModInt binom(int n, int r) {
141+
static ModInt binom(long long n, long long r) {
142142
static long long bruteforce_times = 0;
143143

144144
if (r < 0 or n < r) return ModInt(0);
145145
if (n <= bruteforce_times or n < (int)facs.size()) return ModInt::nCr(n, r);
146146

147147
r = std::min(r, n - r);
148+
assert((int)r == r);
148149

149150
ModInt ret = ModInt::facinv(r);
150151
for (int i = 0; i < r; ++i) ret *= n - i;
@@ -155,6 +156,7 @@ template <int md> struct ModInt {
155156

156157
// Multinomial coefficient, (k_1 + k_2 + ... + k_m)! / (k_1! k_2! ... k_m!)
157158
// Complexity: O(sum(ks))
159+
// Verify: https://yukicoder.me/problems/no/3178
158160
template <class Vec> static ModInt multinomial(const Vec &ks) {
159161
ModInt ret{1};
160162
int sum = 0;
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
#pragma once
2+
#include <algorithm>
3+
#include <cassert>
4+
#include <numeric>
5+
#include <vector>
6+
7+
// (i, 0) (0 <= i < bottom.size()) -> (dx_init + j, y) (0 <= j < len)
8+
// Input: bottom[i] = Initial weight at (i, 0)
9+
// Output: top[j] = weight at (dx_init + j, y) after transition
10+
template <class MODINT>
11+
std::vector<MODINT>
12+
NorthEastLatticePathsParallel(const std::vector<MODINT> &bottom, long long dx_init, long long y,
13+
int len, auto convolve) {
14+
const long long min_x = std::max(dx_init, 0LL), max_x = dx_init + len - 1;
15+
if (max_x < 0 or y < 0) return std::vector<MODINT>(len);
16+
17+
const long long min_shift = std::max<long long>(0, min_x - ((long long)bottom.size() - 1)),
18+
max_shift = max_x;
19+
20+
std::vector<MODINT> trans(max_shift - min_shift + 1);
21+
for (int i = 0; i < (int)trans.size(); ++i)
22+
trans[i] = MODINT::binom(min_shift + i + y, y); // can be made faster if needed
23+
std::vector<MODINT> top = convolve(trans, bottom);
24+
25+
top.erase(top.begin(), top.begin() + std::min<long long>(min_x, (long long)bottom.size() - 1));
26+
top.resize(max_x - min_x + 1);
27+
if (dx_init < 0) {
28+
std::vector<MODINT> tmp(-dx_init);
29+
top.insert(top.begin(), tmp.begin(), tmp.end());
30+
}
31+
top.shrink_to_fit();
32+
assert((int)top.size() == len);
33+
34+
return top;
35+
}
36+
37+
// (i, 0) (0 <= i < bottom.size()) -> (x, dy_init + j) (0 <= j < len)
38+
// Input: bottom[i] = Initial weight at (i, 0)
39+
// Output: right[j] = weight at (x, dy_init + j) after transition
40+
template <class MODINT>
41+
std::vector<MODINT> NorthEastLatticePathsVertical(const std::vector<MODINT> &bottom, int x,
42+
int dy_init, int len, auto convolve) {
43+
const int ylo = std::max(dy_init, 0), yhi = dy_init + len;
44+
if (yhi <= 0 or x < 0) return std::vector<MODINT>(len);
45+
46+
// (i, 0) -> (x, y) : binom(x - i, y)
47+
// f[i] -> g[x + y - ylo] : (x + y - i)! / (x - i)! y!
48+
std::vector<MODINT> tmp = bottom;
49+
if ((int)tmp.size() > x + 1) tmp.resize(x + 1);
50+
51+
for (int i = 0; i < (int)tmp.size(); ++i) tmp[i] *= MODINT::facinv(x - i);
52+
53+
std::vector<MODINT> trans(x + yhi);
54+
for (int i = 0; i < (int)trans.size(); ++i) trans[i] = MODINT::fac(i + ylo);
55+
tmp = convolve(trans, tmp);
56+
57+
std::vector<MODINT> right(yhi - ylo);
58+
for (int y = ylo; y < yhi; ++y) right.at(y - ylo) = tmp.at(x + y - ylo) * MODINT::facinv(y);
59+
60+
if (dy_init < 0) {
61+
std::vector<MODINT> tmp(-dy_init);
62+
right.insert(right.begin(), tmp.begin(), tmp.end());
63+
}
64+
right.shrink_to_fit();
65+
assert((int)right.size() == len);
66+
67+
return right;
68+
}
69+
70+
// Solve DP below in O((h + w)log(h + w)) (if `convolve()` is O(n log n))
71+
// 1. dp[0, 0:h] += left[:]
72+
// 2. dp[0:w, 0] += bottom[:]
73+
// 3. dp[i, j] := dp[i-1, j] + dp[i, j-1]
74+
// 4. return (right = dp[w-1, 0:h], top = dp[0:w, h-1]
75+
template <class MODINT>
76+
auto NorthEastLatticePathsInRectangle(const std::vector<MODINT> &left,
77+
const std::vector<MODINT> &bottom, auto convolve) {
78+
struct Result {
79+
std::vector<MODINT> right, top;
80+
};
81+
82+
const int h = left.size(), w = bottom.size();
83+
if (h == 0 or w == 0) return Result{left, bottom};
84+
85+
auto right = NorthEastLatticePathsParallel(left, 0, w - 1, h, convolve);
86+
auto top = NorthEastLatticePathsParallel(bottom, 0, h - 1, w, convolve);
87+
88+
const auto right2 = NorthEastLatticePathsVertical(bottom, w - 1, 0, h, convolve);
89+
for (int i = 0; i < (int)right2.size(); ++i) right[i] += right2[i];
90+
91+
const auto top2 = NorthEastLatticePathsVertical(left, h - 1, 0, w, convolve);
92+
for (int i = 0; i < (int)top2.size(); ++i) top[i] += top2[i];
93+
94+
return Result{right, top};
95+
}
96+
97+
// a) Lattice paths from (*, 0) / (0, *). Count paths ending at (w - 1, *) or absorbed at (i, ub[i])s.
98+
// b) In other words, count sequences satisfying 0 <= a_i < ub[i]
99+
// c) In other words, solve DP below:
100+
// 1. dp[0:w, 0] += bottom[:], dp[0, 0:ub[0]] += left[:]
101+
// 2. dp[i, j + 1] += dp[i, j]
102+
// 3. dp[i + 1, j] += dp[i, j] (j < ub[i])
103+
// 4. return dp[w-1, 0:ub[w-1]] as right, dp[i, ub[i] - 1] as top
104+
// Complexity: O((h + w) (log(h + w))^2) (if `convolve()` is O(n log n))
105+
// Requirement: ub is non-decreasing
106+
template <class MODINT>
107+
auto NorthEastLatticePathsBounded(const std::vector<int> &ub, const std::vector<MODINT> &left,
108+
const std::vector<MODINT> &bottom, auto convolve) {
109+
struct Result {
110+
std::vector<MODINT> right, top;
111+
};
112+
113+
assert(ub.size() == bottom.size());
114+
if (ub.empty()) return Result{left, {}};
115+
116+
assert(ub.front() == (int)left.size());
117+
assert(ub.front() >= 0);
118+
for (int i = 1; i < (int)ub.size(); ++i) assert(ub[i] >= ub[i - 1]);
119+
120+
if (ub.back() <= 0) return Result{{}, bottom};
121+
122+
if (const int n = bottom.size(); n > 64 and ub.back() > 64) { // 64: parameter
123+
const int l = n / 2, r = n - l;
124+
const int b = ub[l];
125+
126+
auto [right1, top1] = NorthEastLatticePathsBounded<MODINT>(
127+
{ub.begin(), ub.begin() + l}, left, {bottom.begin(), bottom.begin() + l}, convolve);
128+
right1.resize(b);
129+
auto [right, out2] = NorthEastLatticePathsInRectangle<MODINT>(
130+
right1, {bottom.begin() + l, bottom.end()}, convolve);
131+
132+
std::vector<int> ub_next(r);
133+
for (int i = 0; i < r; ++i) ub_next[i] = ub[l + i] - b;
134+
const auto [right3, top3] =
135+
NorthEastLatticePathsBounded<MODINT>(ub_next, {}, out2, convolve);
136+
right.insert(right.end(), right3.begin(), right3.end());
137+
top1.insert(top1.end(), top3.begin(), top3.end());
138+
return Result{right, top1};
139+
} else {
140+
std::vector<MODINT> dp = left;
141+
std::vector<MODINT> top = bottom;
142+
dp.reserve(ub.back());
143+
for (int i = 0; i < n; ++i) {
144+
dp.resize(ub[i], 0);
145+
if (dp.empty()) continue;
146+
dp[0] += bottom[i];
147+
for (int j = 1; j < (int)dp.size(); ++j) dp[j] += dp[j - 1];
148+
top[i] = dp.back();
149+
}
150+
return Result{dp, top};
151+
}
152+
}
153+
154+
// Lattice paths from (0, *). Count paths ending at (w - 1, *). In other words, solve DP below:
155+
// 1. dp[0, lb[0]:ub[0]] += left[:]
156+
// 2. dp[i, j + 1] += dp[i, j] (j + 1 < ub[i])
157+
// 3. dp[i + 1, j] += dp[i, j] (lb[i + 1] <= j)
158+
// 4. return dp[w-1, lb[w-1]:ub[w-1]]
159+
// Complexity: O((h + w) (log(h + w))^2) (if `convolve()` is O(n log n))
160+
// Requirement: lb/ub is non-decreasing
161+
template <class MODINT>
162+
std::vector<MODINT>
163+
NorthEastLatticePathsBothBounded(const std::vector<int> &lb, const std::vector<int> &ub,
164+
const std::vector<MODINT> &left, auto convolve) {
165+
assert(lb.size() == ub.size());
166+
167+
const int n = ub.size();
168+
if (n == 0) return left;
169+
170+
assert((int)left.size() == ub[0] - lb[0]);
171+
for (int i = 1; i < n; ++i) {
172+
assert(lb[i - 1] <= lb[i]);
173+
assert(ub[i - 1] <= ub[i]);
174+
}
175+
176+
for (int i = 0; i < n; ++i) {
177+
if (lb[i] >= ub[i]) return std::vector<MODINT>(ub.back() - lb.back());
178+
}
179+
180+
int x = 0;
181+
std::vector<MODINT> dp_left = left;
182+
std::vector<int> tmp_ub;
183+
while (true) {
184+
dp_left.resize(ub[x] - lb[x], MODINT{0});
185+
186+
const int x1 = std::upper_bound(ub.begin() + x + 1, ub.begin() + n, ub[x]) - ub.begin();
187+
const int x2 = std::lower_bound(lb.begin() + x1, lb.begin() + n, ub[x]) - lb.begin();
188+
const int x3 = std::upper_bound(lb.begin() + x2, lb.begin() + n, ub[x]) - lb.begin();
189+
190+
tmp_ub.assign(dp_left.size(), x2 - x);
191+
for (int i = x2 - 1; i >= x; --i) {
192+
if (const int d = lb[i] - lb[x] - 1; d >= 0 and d < (int)tmp_ub.size()) {
193+
tmp_ub[d] = i - x;
194+
}
195+
}
196+
for (int j = (int)tmp_ub.size() - 1; j; --j)
197+
tmp_ub[j - 1] = std::min(tmp_ub[j - 1], tmp_ub[j]);
198+
199+
auto [next_dp, southeast] = NorthEastLatticePathsBounded(
200+
tmp_ub, std::vector<MODINT>(tmp_ub.front()), dp_left, convolve);
201+
next_dp.erase(next_dp.begin(), next_dp.begin() + (x1 - x));
202+
assert((int)next_dp.size() == x2 - x1);
203+
204+
if (x1 < x3) {
205+
next_dp.resize(x3 - x1, MODINT{0});
206+
tmp_ub.resize(x3 - x1);
207+
for (int i = x1; i < x3; ++i) tmp_ub[i - x1] = ub[i] - ub[x];
208+
next_dp = NorthEastLatticePathsBounded(
209+
tmp_ub, std::vector<MODINT>(tmp_ub.front()), next_dp, convolve)
210+
.right;
211+
} else {
212+
next_dp.clear();
213+
}
214+
215+
if (x3 >= n) {
216+
std::vector<MODINT> ret = southeast;
217+
ret.insert(ret.end(), next_dp.begin(), next_dp.end());
218+
ret.erase(ret.begin(), ret.begin() + (lb[n - 1] - lb[x]));
219+
assert((int)ret.size() == ub[n - 1] - lb[n - 1]);
220+
return ret;
221+
} else {
222+
next_dp.erase(next_dp.begin(), next_dp.begin() + (lb[x3] - ub[x]));
223+
x = x3;
224+
dp_left = std::move(next_dp);
225+
}
226+
}
227+
}
228+
229+
// Count nonnegative non-decreasing integer sequence `a` satisfying a[i] < ub[i]
230+
// Complexity: O(n log^2(n)) (if `convolve()` is O(n log n))
231+
template <class MODINT> MODINT CountBoundedMonotoneSequence(std::vector<int> ub, auto convolve) {
232+
const int n = ub.size();
233+
assert(n > 0);
234+
for (int i = n - 1; i; --i) ub[i - 1] = std::min(ub[i - 1], ub[i]);
235+
if (ub.front() <= 0) return MODINT{0};
236+
237+
std::vector<MODINT> bottom(ub.size()), left(ub.front());
238+
bottom.front() = 1;
239+
std::vector<MODINT> ret = NorthEastLatticePathsBounded(ub, left, bottom, convolve).right;
240+
return std::accumulate(ret.begin(), ret.end(), MODINT{});
241+
}
242+
243+
// Count nonnegative non-decreasing integer sequence `a` satisfying lb[i] <= a[i] < ub[i]
244+
// Complexity: O(n log^2(n)) (if `convolve()` is O(n log n))
245+
// https://noshi91.hatenablog.com/entry/2023/07/21/235339
246+
// Verify: https://judge.yosupo.jp/problem/number_of_increasing_sequences_between_two_sequences
247+
template <class MODINT>
248+
MODINT CountBoundedMonotoneSequence(std::vector<int> lb, std::vector<int> ub, auto convolve) {
249+
assert(lb.size() == ub.size());
250+
251+
const int n = ub.size();
252+
if (n == 0) return MODINT{1};
253+
254+
for (int i = 1; i < n; ++i) lb[i] = std::max(lb[i - 1], lb[i]);
255+
for (int i = n - 1; i; --i) ub[i - 1] = std::min(ub[i - 1], ub[i]);
256+
257+
for (int i = 0; i < n; ++i) {
258+
if (lb[i] >= ub[i]) return MODINT{0};
259+
}
260+
261+
const int k = lb.back();
262+
lb.insert(lb.begin(), lb.front()); // len(lb) == n + 1
263+
lb.pop_back();
264+
265+
std::vector<MODINT> init(ub.front() - lb.front());
266+
init.at(0) = 1;
267+
268+
auto res = NorthEastLatticePathsBothBounded(lb, ub, init, convolve);
269+
res.erase(res.begin(), res.begin() + (k - lb.back()));
270+
return std::accumulate(res.begin(), res.end(), MODINT{});
271+
}

0 commit comments

Comments
 (0)