diff --git a/content/6_Advanced/Eulers_Formula.mdx b/content/6_Advanced/Eulers_Formula.mdx index 34ce66c44c..d7070ecc4e 100644 --- a/content/6_Advanced/Eulers_Formula.mdx +++ b/content/6_Advanced/Eulers_Formula.mdx @@ -1,7 +1,7 @@ --- id: eulers-formula title: "Euler's Formula" -author: Benjamin Qi +author: Benjamin Qi, Aarush Penugonda description: A formula for finding the number of faces in a planar graph. prerequisites: - dsu @@ -11,91 +11,536 @@ frequency: 1 ## Introduction - +A planar graph is a graph that can be drawn on a plane without any edges crossing. In other words, it can be embedded in the plane such that no two edges intersect except at their endpoints. + + + + 6.024J course notes + + + Wiki Definition + + + + + +## Euler's Formula + +Euler's Formula states that any correct embedding of a connected planar graph satistfies:- + +
$$ V − E + F = 2 $$
+ +$$ V = Vertices $$ + +$$ E = Edges $$ + +$$ F = Faces $$ ## Example 1 +## Explanation + +### Intuition + +In this problem, we're asked to count the number of contiguous areas of cells on +several flat rectangles. Such areas are separated by river segments and +rectangle boundaries. + +Where else do we count the number of areas on a flat surface? + +That's right - we use Euler's formula to count the number of faces of a +[planar graph](http://discrete.openmathbooks.org/more/mdm/sec_planar.html). This +suggests that we should turn our rectangles into planar graphs. + +### Making the Planar Graph + +We can turn a rectangle into a planar graph as so: + +- Put temporary river segments outside the border of the rectangle. +- For each river segment, we insert its 4 corners into a set of nodes and its 4 + sides into a set of edges. + +Notice how the resulting graph is planar, so we can apply Euler's formula. + +### Applying Euler's Formula + +For a planar graph, Euler's formula is given as $F = E - V + 1 + C$, where $F$ +is the number of faces (including the background face), $E$ is the number of +edges, $V$ is the number of vertices, and $C$ is the number of connected +components. + +Notice how $F$ in our planar graph is equal to $1 + R + A$, where $R$ is the +number of river segments and $A$ is the answer to the query. This means we must +subtract $R + 1$ from $F$ to get $A$. + +Since the whole river is a big connected component, we can just check whether +the river touches the bounding rectangle to determine $C$. + +Finding $E$, $V$, and $R$ is a lot more complicated though. + +### Finding $E$, $V$, and $R$ + +To find $E$, $V$, and $R$, we can use a data structure that can handle 2D range +queries efficiently. + +However, the coordinates of the grid can get very large, so a simple 2D BIT or +segment tree won't work here. + +To work around this, we can either use a 2D BIT with coordinate compression or a +persistent segment tree. See the sections on +[offline 2D sum queries](/plat/2DRQ#2d-offline-sum-queries) or +[persistent segment trees](/adv/persistent) for more details. + +## Implementation + +With a persistent segment tree. + +**Time Complexity:** $\mathcal{O}(N \log N)$ - +**Memory Complexity:** $\mathcal{O}(N \log N)$ + +```cpp + +const int MAXN = 2e5, MAXSEGMENT = (6e5 + 9) * 19 + 1; + +int cnt = 1, segtree[MAXSEGMENT], left_c[MAXSEGMENT], right_c[MAXSEGMENT]; + +struct Segtree { + set data[MAXN + 1]; + int roots[MAXN + 2]; + + void add(int x, int y) { data[x].insert(y); } + + void build() { + FOR(i, 1, MAXN + 1) { + roots[i + 1] = roots[i]; + for (int j : data[i]) update(j, roots[i + 1]); + } + } + + void update(int pos, int &node, int l = 1, int r = MAXN) { + segtree[cnt] = segtree[node] + 1; + left_c[cnt] = left_c[node]; + right_c[cnt] = right_c[node]; + node = cnt++; + + if (l == r) return; + int mid = (l + r) / 2; + if (pos > mid) update(pos, right_c[node], mid + 1, r); + else update(pos, left_c[node], l, mid); + } + + int query(int l1, int r1, int l2, int r2) { + if (l2 > r2) return 0; + return query(l2, r2, roots[r1 + 1], 1, MAXN) - + query(l2, r2, roots[l1], 1, MAXN); + } + int query(int a, int b, int node, int l, int r) { + if (a > r || b < l) return 0; + if (a <= l && b >= r) return segtree[node]; + int mid = (l + r) / 2; + return query(a, b, left_c[node], l, mid) + + query(a, b, right_c[node], mid + 1, r); + } +} vertices, edges_horiz, edges_vert, rivers; + +int mx_r, mn_r, mx_c, mn_c; + +void add_river(int x, int y) { + vertices.add(x, y); + vertices.add(x + 1, y); + vertices.add(x, y + 1); + vertices.add(x + 1, y + 1); + edges_horiz.add(x, y); + edges_horiz.add(x + 1, y); + edges_vert.add(x, y); + edges_vert.add(x, y + 1); + rivers.add(x, y); +} + +void init(int R, int C, int sr, int sc, int M, char *S) { + add_river(sr, sc); + mx_r = mn_r = sr; + mx_c = mn_c = sc; + FOR(i, 0, M) { + if (S[i] == 'N') sr--; + if (S[i] == 'E') sc++; + if (S[i] == 'S') sr++; + if (S[i] == 'W') sc--; + add_river(sr, sc); + mx_r = max(mx_r, sr); + mn_r = min(mn_r, sr); + mx_c = max(mx_c, sc); + mn_c = min(mn_c, sc); + } + vertices.build(); + edges_horiz.build(); + edges_vert.build(); + rivers.build(); +} + +int colour(int ar, int ac, int br, int bc) { + int e = + edges_horiz.query(ar + 1, br, ac, bc) + edges_vert.query(ar, br, ac + 1, bc); + int v = vertices.query(ar + 1, br, ac + 1, bc); + int r = rivers.query(ar, br, ac, bc); + int c = (ar >= mn_r || br <= mx_r || ac >= mn_c || bc <= mx_c ? 1 : 2); + return e - v + c - r; +} +``` ## Example 2 - +## Explanation + +This problem involves a 2D grid. The code tracks connected region of points using heights. We will use Disjoint Set Union (DSU). As we process points by increasing height, we merge them into regions and update the answer based on their size. The logic here mirrors an application of Euler's formula for planar graphs, where we maintain +boundaries (faces) as we mege points (vertices) and check their connectivity (edges). +## Implementation + +With Euler's Formula + +**Time Complexity:** $\mathcal{O}(N^2 \log N)$ + +**Memory Complexity:** $\mathcal{O}(N^2)$ ```cpp +#include +using namespace std; + +using ll = long long; +using ld = long double; +using db = double; +using str = string; // yay python! + +using pi = pair; +using pl = pair; +using pd = pair; + +using vi = vector; +using vb = vector; +using vl = vector; +using vd = vector; +using vs = vector; +using vpi = vector; +using vpl = vector; +using vpd = vector; + +#define tcT template using V = vector; +tcT, size_t SZ > using AR = array; +tcT > using PR = pair; + +// pairs +#define mp make_pair +#define f first +#define s second + +// vectors +// oops size(x), rbegin(x), rend(x) need C++17 +#define sz(x) int((x).size()) +#define bg(x) begin(x) +#define all(x) bg(x), end(x) +#define rall(x) x.rbegin(), x.rend() +#define sor(x) sort(all(x)) +#define rsz resize +#define ins insert +#define ft front() +#define bk back() +#define pb push_back +#define eb emplace_back +#define pf push_front + +#define lb lower_bound +#define ub upper_bound +tcT > int lwb(V &a, const T &b) { return int(lb(all(a), b) - bg(a)); } + +// loops +#define FOR(i, a, b) for (int i = (a); i < (b); ++i) +#define F0R(i, a) FOR(i, 0, a) +#define ROF(i, a, b) for (int i = (b) - 1; i >= (a); --i) +#define R0F(i, a) ROF(i, 0, a) +#define trav(a, x) for (auto &a : x) + +const int MOD = 1e9 + 7; // 998244353; +const int MX = 2e5 + 5; +const ll INF = 1e18; // not too close to LLONG_MAX +const ld PI = acos((ld)-1); +const int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1}; // for every grid problem!! +mt19937 rng((uint32_t)chrono::steady_clock::now().time_since_epoch().count()); +template using pqg = priority_queue, greater>; + +// bitwise ops +// also see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html +constexpr int pct(int x) { return __builtin_popcount(x); } // # of bits set +constexpr int bits(int x) { // assert(x >= 0); // make C++11 compatible until + // USACO updates ... + return x == 0 ? 0 : 31 - __builtin_clz(x); +} // floor(log2(x)) +constexpr int p2(int x) { return 1 << x; } +constexpr int msk2(int x) { return p2(x) - 1; } + +ll cdiv(ll a, ll b) { + return a / b + ((a ^ b) > 0 && a % b); +} // divide a by b rounded up +ll fdiv(ll a, ll b) { + return a / b - ((a ^ b) < 0 && a % b); +} // divide a by b rounded down + +tcT > bool ckmin(T &a, const T &b) { return b < a ? a = b, 1 : 0; } // set a = min(a,b) +tcT > bool ckmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; } + +tcTU > T fstTrue(T lo, T hi, U f) { + hi++; + assert(lo <= hi); // assuming f is increasing + while (lo < hi) { // find first index such that f is true + T mid = lo + (hi - lo) / 2; + f(mid) ? hi = mid : lo = mid + 1; + } + return lo; +} +tcTU > T lstTrue(T lo, T hi, U f) { + lo--; + assert(lo <= hi); // assuming f is decreasing + while (lo < hi) { // find first index such that f is true + T mid = lo + (hi - lo + 1) / 2; + f(mid) ? lo = mid : hi = mid - 1; + } + return lo; +} +tcT > void remDup(vector &v) { // sort and remove duplicates + sort(all(v)); + v.erase(unique(all(v)), end(v)); +} +tcTU > void erase(T &t, const U &u) { // don't erase + auto it = t.find(u); + assert(it != end(t)); + t.erase(it); +} // element that doesn't exist from (multi)set + +// INPUT +#define tcTUU tcT, class... U +tcT > void re(complex &c); +tcTU > void re(pair &p); +tcT > void re(V &v); +tcT, size_t SZ > void re(AR &a); + +tcT > void re(T &x) { cin >> x; } +void re(db &d) { + str t; + re(t); + d = stod(t); +} +void re(ld &d) { + str t; + re(t); + d = stold(t); +} +tcTUU > void re(T &t, U &...u) { + re(t); + re(u...); +} + +tcT > void re(complex &c) { + T a, b; + re(a, b); + c = {a, b}; +} +tcTU > void re(pair &p) { re(p.f, p.s); } +tcT > void re(V &x) { trav(a, x) re(a); } +tcT, size_t SZ > void re(AR &x) { trav(a, x) re(a); } +tcT > void rv(int n, V &x) { + x.rsz(n); + re(x); +} + +// TO_STRING +#define ts to_string +str ts(char c) { return str(1, c); } +str ts(const char *s) { return (str)s; } +str ts(str s) { return s; } +str ts(bool b) { +#ifdef LOCAL + return b ? "true" : "false"; +#else + return ts((int)b); +#endif +} +tcT > str ts(complex c) { + stringstream ss; + ss << c; + return ss.str(); +} +str ts(V v) { + str res = "{"; + F0R(i, sz(v)) res += char('0' + v[i]); + res += "}"; + return res; +} +template str ts(bitset b) { + str res = ""; + F0R(i, SZ) res += char('0' + b[i]); + return res; +} +tcTU > str ts(pair p); +tcT > str ts(T v) { // containers with begin(), end() +#ifdef LOCAL + bool fst = 1; + str res = "{"; + for (const auto &x : v) { + if (!fst) res += ", "; + fst = 0; + res += ts(x); + } + res += "}"; + return res; +#else + bool fst = 1; + str res = ""; + for (const auto &x : v) { + if (!fst) res += " "; + fst = 0; + res += ts(x); + } + return res; + +#endif +} +tcTU > str ts(pair p) { +#ifdef LOCAL + return "(" + ts(p.f) + ", " + ts(p.s) + ")"; +#else + return ts(p.f) + " " + ts(p.s); +#endif +} + +// OUTPUT +tcT > void pr(T x) { cout << ts(x); } +tcTUU > void pr(const T &t, const U &...u) { + pr(t); + pr(u...); +} +void ps() { pr("\n"); } // print w/ spaces +tcTUU > void ps(const T &t, const U &...u) { + pr(t); + if (sizeof...(u)) pr(" "); + ps(u...); +} + +// DEBUG +void DBG() { cerr << "]" << endl; } +tcTUU > void DBG(const T &t, const U &...u) { + cerr << ts(t); + if (sizeof...(u)) cerr << ", "; + DBG(u...); +} +#ifdef LOCAL // compile with -DLOCAL, chk -> fake assert +#define dbg(...) \ + cerr << "Line(" << __LINE__ << ") -> [" << #__VA_ARGS__ << "]: [", DBG(__VA_ARGS__) +#define chk(...) \ + if (!(__VA_ARGS__)) \ + cerr << "Line(" << __LINE__ << ") -> function(" << __FUNCTION__ \ + << ") -> CHK FAILED: (" << #__VA_ARGS__ << ")" << "\n", \ + exit(0); +#else +#define dbg(...) 0 +#define chk(...) 0 +#endif + +void setPrec() { cout << fixed << setprecision(15); } +void unsyncIO() { cin.tie(0)->sync_with_stdio(0); } +// FILE I/O +void setIn(str s) { freopen(s.c_str(), "r", stdin); } +void setOut(str s) { freopen(s.c_str(), "w", stdout); } +void setIO(str s = "") { + unsyncIO(); + setPrec(); + // cin.exceptions(cin.failbit); + // throws exception when do smth illegal + // ex. try to read letter into int + if (sz(s)) setIn(s + ".in"), setOut(s + ".out"); // for USACO +} + int N, h[750][750]; -ll ans; -vector> v; -int hsh(int a, int b) { return N * a + b; } +int hsh(pi a) { return N * a.f + a.s; } -const int xd[4] = {1, 0, -1, 0}, yd[4] = {0, 1, 0, -1}; +bool valid(pi a) { return 0 <= a.f && a.f < N && 0 <= a.s && a.s < N; } template struct DSU { - int par[SZ], sz[SZ], measure[SZ]; - vi comp[SZ]; - DSU() { F0R(i, SZ) par[i] = i, sz[i] = 1, measure[i] = 1; } - bool valid(int b, int c) { return b >= 0 && b < N && c >= 0 && c < N; } - bool ok(int a, int b, int c) { - if (!valid(b, c)) return 0; - return par[hsh(b, c)] == a; - } - void addPoint(int x, pi t) { - par[hsh(t.f, t.s)] = x; - measure[x]++; - F0R(i, 4) { - if (ok(x, t.f + xd[i], t.s + yd[i])) { - measure[x]--; - int j = (i + 1) % 4; - if (ok(x, t.f + xd[j], t.s + yd[j]) && - ok(x, t.f + xd[j] + xd[i], t.s + yd[j] + yd[i])) - measure[x]++; - } - } - comp[x].pb(hsh(t.f, t.s)); + int par[SZ], sz[SZ], hole[SZ], numComp = 1; + + DSU() { F0R(i, SZ) par[i] = i, sz[i] = 1; } + + int get(int x) { // path compression + if (par[x] != x) par[x] = get(par[x]); + return par[x]; } - void unite(pi x, pi y) { // union-by-rank - int X = hsh(x.f, x.s), Y = hsh(y.f, y.s); - if (par[X] == par[Y]) return; - X = par[X], Y = par[Y]; - if (sz(comp[X]) < sz(comp[Y])) swap(X, Y); - trav(t, comp[Y]) addPoint(X, {t / N, t % N}); - comp[Y].clear(); + + void unite(pi a, pi b) { // union-by-rank + int A = get(hsh(a)), B = get(hsh(b)); + if (A == B) return; + if (sz[A] < sz[B]) swap(A, B); + numComp--; + sz[A] += sz[B]; + hole[A] += hole[B]; + par[B] = A; } }; -DSU<750 * 750> D; -bool ok[750][750]; +DSU<750 * 750 + 5> A, B; -void solve(int x, int y) { - ok[x][y] = 1; - F0R(i, 4) { - int X = x + xd[i], Y = y + yd[i]; - if (X < 0 || X >= N || Y < 0 || Y >= N) continue; - if (!ok[X][Y]) continue; - D.unite({x, y}, {X, Y}); - } -} +int xd[4] = {0, 1, 0, -1}, yd[4] = {1, 0, -1, 0}; int main() { setIO("valleys"); re(N); - F0R(i, N) F0R(j, N) { - re(h[i][j]); - v.pb({h[i][j], {i, j}}); - D.comp[hsh(i, j)].pb(hsh(i, j)); + F0R(i, N) F0R(j, N) re(h[i][j]); + vpi v; + F0R(i, N) F0R(j, N) v.pb({i, j}); + sort(all(v), [](pi a, pi b) { return h[a.f][a.s] < h[b.f][b.s]; }); + + vi rec = {1}; + R0F(i, sz(v)) { + A.numComp++; + FOR(j, -1, 2) FOR(k, -1, 2) { + pi x = {v[i].f + j, v[i].s + k}; + if (!valid(x)) A.unite(v[i], {N, 0}); + else if (h[x.f][x.s] > h[v[i].f][v[i].s]) A.unite(v[i], x); + } + rec.pb(A.numComp); } - sort(all(v)); - F0R(i, sz(v)) { - solve(v[i].s.f, v[i].s.s); - pi p = v[i].s; - int q = D.par[hsh(p.f, p.s)]; - if (D.measure[q] == 1) ans += sz(D.comp[q]); + reverse(all(rec)); + + ll ret = 0; + F0R(i, sz(v)) { // maintain number of holes in current component + F0R(j, 4) { + pi x = {v[i].f + xd[j], v[i].s + yd[j]}; + if (!valid(x)) continue; + if (h[x.f][x.s] < h[v[i].f][v[i].s]) B.unite(v[i], x); + } + int H = B.get(hsh(v[i])); + B.hole[H] += rec[i + 1] - rec[i]; + if (B.hole[H] == 0) { ret += B.sz[H]; } } - cout << ans; + pr(ret); } ```