Skip to content

Commit d0ffbb8

Browse files
committed
update
1 parent 88f2ca2 commit d0ffbb8

File tree

7 files changed

+219
-22
lines changed

7 files changed

+219
-22
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
モノイド $(T,\cdot,e),(F,\circ,\mathrm{id})$ があり,$F$ は $T$ に左から作用するとする.
2+
3+
- 実際には各種演算は計算で出てくる範囲で定義されていれば構わない.
4+
5+
長さ $N$ の $T$ の列 $A=(A_0,A_1,\dots,A_{N-1})$ に対し,空間計算量 $\Theta(N)$ のもとで以下の操作を行える.
6+
7+
- `set(p, x)`:$A_p \gets x$ とする.$O(\log N)$ 時間.
8+
- `get(p)`:$A_p$ を取得する.$O(\log N)$ 時間.
9+
- `apply(l, r, f)`:$i=l,l+1,\dots,r-1$ に対し $A_i \gets f A_i$ とする.$O(\log N)$ 時間.
10+
- `prod(l, r)`:$A_l\cdot A_{l+1}\cdot\cdots\cdot A_{r-1}$ を取得する.$O(\log N)$ 時間.
11+
- 二分探索:$O(\log N)$ 時間.$\mathrm{cond}(e)=\mathrm{true}$ を要求する.
12+
- `max_right(l, cond)`:$\mathrm{cond}(A_l \cdot A_{l+1} \cdot \cdots \cdot A_{r-1})$ が真となる最大の $r$ を返す.
13+
- `min_left(r, cond)`:$\mathrm{cond}(A_l \cdot A_{l+1} \cdot \cdots \cdot A_{r-1})$ が真となる最小の $l$ を返す.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
通常の Union Find のほかに列 $(p_0,p_1,\dots,p_{n-1})$ に関する情報を管理する.
2+
3+
- `unite(u, v, w)`:$p_u-p_v=w$ という情報が与えられる.それまでに与えられた valid な情報と矛盾しないとき valid な情報と呼ぶことにする.与えられた情報が valid な情報か否かを返す.
4+
- `diff(u, v)`:$p_u-p_v$ の値が valid な情報から一意に定まる場合その値を,定まらない場合はその旨を報告する.
5+
6+
## アルゴリズム
7+
8+
Union Find の内部において $p_v$ から $v$ を含む連結成分の根の $p$ の値を減じたものを管理する.

docs/union-find/union-find.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
$n$ 頂点 $0$ 辺のグラフに対する以下のクエリを処理する.$\alpha$ はアッカーマン関数の逆関数.
2+
3+
- `find(v)`:頂点 $v$ を含む連結成分の代表頂点を返す.$O(\alpha(n))$ 時間.
4+
- `size(v)`:頂点 $v$ を含む連結成分の頂点数を返す.$O(\alpha(n))$ 時間.
5+
- `same(u, v)`:頂点 $u,v$ が同じ連結成分に含まれるかを返す.$O(\alpha(n))$ 時間.
6+
- `unite(u, v)`:辺 $uv$ を追加する.$O(\alpha(n))$ 時間.
7+
- `unite(u, v, f)` で連結成分マージ時に $f$ を呼び出す.
8+
- `groups()`:連結成分を列挙する.$O(n)$ 時間.
9+
10+
## アルゴリズム
11+
12+
`find` および `unite` が実装できれば他も難しくない.
13+
14+
根付き木として考える.
15+
各頂点の親を示す長さ $n$ の列 $(p_0,\dots,p_{n-1})$ を管理する.
16+
$p_i=i$ ならば頂点 $i$ は根であり,はじめ全ての $i$ に対し $p_i=i$ である.
17+
18+
`find`, `unite` は naive には以下のように実装できる.
19+
20+
```
21+
def find(v):
22+
if p[v] == v: return v
23+
return find(p[v])
24+
def unite(u, v):
25+
u = find(u)
26+
v = find(v)
27+
p[v] = u
28+
```
29+
30+
## 計算量
31+
32+
https://37zigen.com/union-find-complexity-1/
33+
34+
毎回素直に根まで辿り,unite では一方の根をもう一方の根に繋ぐ.
35+
これでは worst $\Theta(n)$ 時間である.
36+
37+
計算量を改善する手法がある.
38+
39+
## path compression
40+
41+
path compression では `find` を以下のようにする.
42+
```
43+
def find(v):
44+
if p[v] == v: return v
45+
return p[v] = find(p[v])
46+
```
47+
一度再帰で通った部分を圧縮している.
48+
49+
このとき各操作がならし $O(\log n)$ 時間になる.
50+
51+
<details>
52+
53+
証明:頂点 $v$ を根とする部分木の大きさを $s_v$ とし,ポテンシャル $\Phi=2\sum_{v}\log_2 s_v$ を考える.
54+
はじめ $\Phi=0$ である.
55+
56+
`unite` については `find` での変化を除くと,ポテンシャルは連結性が変化するときにのみ高々 $2\log_2 n$ だけ増加し,この増加は高々 $n$ 回.
57+
58+
`find` の再帰において頂点 $v_0,v_1,\dots,v_k$ を順に通ったとき($v_k$ が根である),$1\leq i\leq k-1$ について $s_{v_i} \to s_{v_i}-s_{v_{i-1}}$ となる.
59+
ポテンシャルの差分は
60+
$$\begin{align*}
61+
2\sum_{i=1}^{k-1}\left(\log_2(s_{v_i}-s_{v_{i-1}})-\log_2 s_{v_i}\right)
62+
&=2\sum_{i=1}^{k-1}\log_2\left(1-\dfrac{s_{v_{i-1}}}{s_{v_i}}\right)\\
63+
&\leq-2\sum_{i=1}^{k-1}\dfrac{s_{v_{i-1}}}{s_{v_i}}\\
64+
&\leq \log_2 n+1-k
65+
\end{align*}$$
66+
と評価できる.
67+
68+
- 一つ目の不等式は $\log(1-x)\leq -x$ より.
69+
- 二つ目の不等式は $s_{v_i}/s_{v_{i-1}}\geq 2$ なる $i$ が高々 $\log_2 n$ 個であることより.
70+
71+
従って `find` を $q$ 回行ったとき $\Phi\geq 0$ より $\sum k=O((n+q)\log n)$ と評価できる.
72+
73+
</details>
74+
75+
## union by size (rank)
76+
77+
union by size では `unite` を以下のようにする.
78+
```
79+
def unite(u, v):
80+
u = find(u)
81+
v = find(v)
82+
if size(u) < size(v): swap(u, v)
83+
p[v] = u
84+
```
85+
部分木のサイズが小さい方を大きい方の子とする(いわゆるマージテク).
86+
87+
このとき各操作が $O(\log n)$ 時間になる.
88+
89+
<details>
90+
91+
証明:頂点 $p,c$ で $p$ が $c$ の親であるとき $\operatorname{size}(p)\geq 2\operatorname{size}(c)$ が成り立つから,常に各頂点と根の距離は高々 $\log_2 n$ であり,`find` は $O(\log n)$ 時間で行える.
92+
93+
</details>
94+
95+
各頂点の rank を (path compression しない状態での) その頂点を根とする部分木の高さとする.
96+
union by rank は union by size で size を用いている部分を rank に置き換えたもの.
97+
98+
## 組み合わせ
99+
100+
path compression と union by size (rank) を組み合わせるとさらにオーダーが改善し,ならし $O(\alpha(n))$ 時間になることが知られている.
101+
102+
ここで $\alpha$ はアッカーマン関数の逆関数で,現実的な $n$ の範囲で $\alpha(n)\leq 5$.
103+
104+
参考
105+
- [α(m, n) のお勉強 + 定数時間 decremental neighbor query - えびちゃんの日記](https://rsk0315.hatenablog.com/entry/2022/05/04/215704)

fps/taylor-shift.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ FormalPowerSeries<mint> TaylorShift(FormalPowerSeries<mint> f, mint a) {
88
using fps = FormalPowerSeries<mint>;
99
int n = f.size();
1010
using fact = Factorial<mint>;
11+
fact::reserve(n);
1112
for (int i = 0; i < n; i++) f[i] *= fact::fact(i);
1213
reverse(f.begin(), f.end());
1314
fps g(n, mint(1));

modint/factorial.hpp

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,51 @@ struct Factorial {
99
}
1010
static mint inv(int n) {
1111
static long long mod = mint::get_mod();
12-
static vector<mint> _inv({0, 1});
12+
static vector<mint> buf({0, 1});
1313
assert(n != 0);
1414
if (mod != mint::get_mod()) {
1515
mod = mint::get_mod();
16-
_inv = vector<mint>({0, 1});
16+
buf = vector<mint>({0, 1});
1717
}
18-
if (_inv.size() <= n) _inv.reserve(n + 1);
19-
while (_inv.size() <= n) {
20-
long long k = _inv.size(), q = (mod + k - 1) / k;
21-
_inv.push_back(q * _inv[k * q - mod]);
18+
if ((int)buf.size() <= n) buf.reserve(n + 1);
19+
while ((int)buf.size() <= n) {
20+
long long k = buf.size(), q = (mod + k - 1) / k;
21+
buf.push_back(q * buf[k * q - mod]);
2222
}
23-
return _inv[n];
23+
return buf[n];
2424
}
2525
static mint fact(int n) {
2626
static long long mod = mint::get_mod();
27-
static vector<mint> _fact({1, 1});
27+
static vector<mint> buf({1, 1});
2828
assert(n >= 0);
2929
if (mod != mint::get_mod()) {
3030
mod = mint::get_mod();
31-
_fact = vector<mint>({1, 1});
31+
buf = vector<mint>({1, 1});
3232
}
33-
if (_fact.size() <= n) _fact.reserve(n + 1);
34-
while (_fact.size() <= n) {
35-
long long k = _fact.size();
36-
_fact.push_back(_fact.back() * k);
33+
if ((int)buf.size() <= n) buf.reserve(n + 1);
34+
while ((int)buf.size() <= n) {
35+
long long k = buf.size();
36+
buf.push_back(buf.back() * k);
3737
}
38-
return _fact[n];
38+
return buf[n];
3939
}
4040
static mint fact_inv(int n) {
4141
static long long mod = mint::get_mod();
42-
static vector<mint> _fact_inv({1, 1});
42+
static vector<mint> buf({1, 1});
4343
assert(n >= 0);
4444
if (mod != mint::get_mod()) {
4545
mod = mint::get_mod();
46-
_fact_inv = vector<mint>({1, 1});
46+
buf = vector<mint>({1, 1});
4747
}
48-
if (_fact_inv.size() <= n) _fact_inv.reserve(n + 1);
49-
while (_fact_inv.size() <= n) {
50-
long long k = _fact_inv.size();
51-
_fact_inv.push_back(_fact_inv.back() * inv(k));
48+
if ((int)buf.size() <= n) {
49+
inv(n);
50+
buf.reserve(n + 1);
5251
}
53-
return _fact_inv[n];
52+
while ((int)buf.size() <= n) {
53+
long long k = buf.size();
54+
buf.push_back(buf.back() * inv(k));
55+
}
56+
return buf[n];
5457
}
5558
static mint binom(int n, int r) {
5659
if (r < 0 || r > n) return 0;
@@ -81,7 +84,7 @@ struct Factorial {
8184
if (n < 0 || r < 0) return 0;
8285
return r == 0 ? 1 : binom(n + r - 1, r);
8386
}
84-
}; // namespace Factorial
87+
};
8588
/**
8689
* @brief 階乗, 二項係数
8790
*/
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#pragma once
2+
3+
template <class T>
4+
struct PotentializedUnionFind {
5+
private:
6+
vector<int> a;
7+
vector<T> p;
8+
9+
public:
10+
PotentializedUnionFind(int n) : a(n, -1), p(n, 0) {}
11+
int find(int v) {
12+
if (a[v] < 0) return v;
13+
int r = find(a[v]);
14+
p[v] += p[a[v]];
15+
return a[v] = r;
16+
}
17+
int size(int v) { return -a[find(v)]; }
18+
bool same(int u, int v) { return find(u) == find(v); }
19+
// p[u]-p[v]=w
20+
bool unite(int u, int v, T w) {
21+
int x = find(u), y = find(v);
22+
if (x == y) return p[u] - p[v] == w;
23+
w -= p[u], w += p[v];
24+
if (a[x] < a[y]) {
25+
p[y] = p[x] - w;
26+
a[x] += a[y];
27+
a[y] = x;
28+
} else {
29+
p[x] = p[y] + w;
30+
a[y] += a[x];
31+
a[x] = y;
32+
}
33+
return true;
34+
}
35+
// p[u]-p[v]
36+
T diff(int u, int v) { return p[u] - p[v]; }
37+
};
38+
/**
39+
* @brief ポテンシャル付き Union Find
40+
* @docs docs/union-find/potentialized-union-find.md
41+
*/
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#define PROBLEM "https://judge.yosupo.jp/problem/unionfind_with_potential"
2+
3+
#include "template/template.hpp"
4+
#include "modint/modint.hpp"
5+
using mint = ModInt<998244353>;
6+
#include "union-find/potentialized-union-find.hpp"
7+
8+
int main() {
9+
int n, q;
10+
in(n, q);
11+
PotentializedUnionFind<mint> uf(n);
12+
while (q--) {
13+
int type, u, v;
14+
in(type, u, v);
15+
if (type == 0) {
16+
mint w;
17+
in(w);
18+
out(uf.unite(u, v, w));
19+
} else {
20+
if (uf.same(u, v))
21+
out(uf.diff(u, v));
22+
else
23+
out(-1);
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)