題目傳送門
還好比賽時沒開這題。不然很可能隊內互相開始丟 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 連通,也不會增加連通塊。因此新增的連通塊當且僅當在邊界上,且被這一級的詢問矩形包含。
不難注意到,四個邊界上新增的連通塊都是獨立的,因此這裏寫着細節非常少。
現在的問題轉化成計算 $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(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; }