uoj 528 【美团杯2020】分形之美 - 分治
题目传送门
还好比赛时没开这题。不然很可能队内互相开始丢 7.6k 的写题锅。
不难发现以下性质,证明用归纳法易证,或者比较平凡。
性质1 $n = a + b$ 级分形是 $a$ 级分形将其中的 o 和 x 替换为 $b$ 级分形。
性质2 $n$ 级 o 分形中所有的 o 连通,$n$ 分 x 分形中的所有 o 和边界连通。
性质3 不存在两个 x 相邻。
性质4 $x$ 任意一级中的子矩形中的 o 和子矩形边界连通。
传统做法大概是,在上一级找一个极大子矩形使得扩展 1 次后被询问矩形包含,然后处理一下边界。然后细节多得要死.[写锤子.jpeg]
然后窝去抄了一个优质写法。
首先考虑 $xr > xl, yr > yl$ 的情况。
考虑在上一级中找一个极小子矩形满足它扩展一级后包含当前这一级中的询问矩形。
考虑对于一个 o 扩展后不会增加新的连通块,对于一个 x 扩展后,如果扩展出的 o 不在边界上,那么它会和已经有的 o 连通,也不会增加连通块。因此新增的连通块当且仅当在边界上,且被这一级的询问矩形包含。
不难注意到,四个边界上新增的连通块都是独立的,因此这里的写的细节非常少。
现在的问题转化成计算 $xl = xr$ 的情形。显然这个图形关于主对角线对称,所以 $yl = yr$ 的情况和 $xl = xr$ 一样。
这个时候简单讨论一下 $xl$ 模 3 的余数:
- 当 $xl \equiv 1 \pmod{3}$,这个时候 $(xl, y)$ 为 x 当且仅当 $y \equiv 1 \pmod {3}$,简单计算一下就行了。
- 否则和上面(指找一个极小子矩形的那一部分)类似地考虑,然后只用计算 x 的个数以及 o 连通块的个数,或者考虑两个黑点不相邻,假设有 $k$ 个 x,那么 o 连通块个数通过判断一下两端就可以计算出来 o 的连通块数。
时间复杂度大概是 $O(Tn^3)$。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 998244353; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } friend boolean operator == (const Z& a, const Z& b) { return a.v == b.v; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; bool is_x(int n, ll x, ll y) { if (!n) { return true; } int rx = x % 3, ry = y % 3; x /= 3, y /= 3; if (rx == 1 && ry == 1) { return true; } if (!is_x(n - 1, x, y)) { return false; } return !((rx + ry) & 1); } Zi count_line(int n, ll x, ll yl, ll yr) { if (yl > yr) { return 0; } if (!n) { return 1; } int rx = x % 3; if (rx == 1) { return (yr + 2) / 3 - (yl + 1) / 3; } int rl = yl % 3, rr = yr % 3; Zi ret = count_line(n - 1, x / 3, yl / 3, yr / 3); ret = ret + ret; if (rl > 0) ret -= is_x(n, x, yl - 1); if (rl > 1) ret -= is_x(n, x, yl - 2); if (rr < 2) ret -= is_x(n, x, yr + 1); if (rr < 1) ret -= is_x(n, x, yr + 2); return ret; } Zi calc(int n, ll l, ll r, ll u, ll d) { if (!n) { return 0; } if (l == r && u == d) { return !is_x(n, l, u); } if (u == d) { swap(l, u); swap(r, d); } if (l == r) { return count_line(n, l, u, d) + 1 - is_x(n, l, u) - is_x(n, l, d); } int rl = l % 3, rr = r % 3, ru = u % 3, rd = d % 3; Zi ret = calc(n - 1, l = l / 3, r = r / 3, u = u / 3, d = d / 3); if (rl == 0) ret += count_line(n - 1, l, u + (ru == 2), d - (rd == 0)); if (rr == 2) ret += count_line(n - 1, r, u + (ru == 2), d - (rd == 0)); if (ru == 0) ret += count_line(n - 1, u, l + (rl == 2), r - (rr == 0)); if (rd == 2) ret += count_line(n - 1, d, l + (rl == 2), r - (rr == 0)); return ret; } int T, n; ll xl, xr, yl, yr; int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin >> T; while (T--) { cin >> n >> xl >> xr >> yl >> yr; cout << calc(n, --xl, --xr, --yl, --yr).v << '\n'; } return 0; }