從這裏開始
兩年沒摸 oi,補的第一場 agc 不看題解補完了?
感覺這場 agc 可以和 agc 046 掰手腕(指題目無聊程度)
現在都聽不到妹老師妹式吐槽 agc ,sad......
Problem A Antichain of Integer Strings
容易發現先選大的一定不劣。
Code
#include <bits/stdc++.h> using namespace std; int norm(int x) { if (x < 10) { return 0; } int y = 1; while (x > 9) { x /= 10; y *= 10; } return y; } int T, L, R; void solve() { scanf("%d%d", &L, &R); int nl = norm(L); int nr = norm(R); if (nl == nr) { printf("%d\n", R - L + 1); } else { int cnt = R - nr + 1; int app = nr - max(max(L, R / 10 + 1), cnt); printf("%d\n", cnt + max(app, 0)); } } int main() { scanf("%d", &T); while (T--) { solve(); } return 0; }
Problem B 2A + x
設最大值爲 $a$,最小值爲 $b$。如果 $2b > a$ 考慮 2 種情況:
- $a - b \geq x$,這種情況的話不可能將 $a, b$ 同時操作,否則它們的差不會減小。同時操作也不會使得 $2b - a$ 減小。
- $a - b < x$,這種情況同時操作 $a, b$ 能夠讓最後差變爲 0。
前者顯然在最大值不太大的時候就會產生最優答案。考慮每個數進行操作後可能得到的區間,拿個掃描線去跑就行了。
後者的話多做幾輪,如果答案減小就輸出 0 就行了。時間複雜度 $O(n\log n \log V)$
雖然感覺能直接貪心。
Code
#include <bits/stdc++.h> using namespace std; #define ll long long const ll V = 1e10; const int M = 1 << 17, S = M << 1 | 5; typedef class Event { public: ll l, r; int id; Event(ll l, ll r, int id) : l(l), r(r), id(id){ } bool operator < (Event b) const { return r > b.r; } } Event; int n, X; ll f[S]; void push_up(int p) { f[p] = max(f[p << 1], f[p << 1 | 1]); } void update(int p, ll v) { p |= M; f[p] = v; while (p >>= 1) push_up(p); } priority_queue<Event> Q; int main() { scanf("%d%d", &n, &X); for (int i = 1, x; i <= n; i++) { scanf("%d", &x); Q.push(Event(x, x, i)); update(i, x); } ll ans = V; while (!Q.empty()) { ll L = Q.top().r; if (L >= V) { break; } ans = min(ans, max(f[1] - L, 0ll)); while (!Q.empty() && Q.top().r == L) { auto e = Q.top(); Q.pop(); ll nl = e.l * 2; ll nr = e.r * 2 + X; update(e.id, nl); Q.push(Event(nl, nr, e.id)); } } ll oldans = ans; while (!Q.empty()) { ll L = Q.top().r; if (L >= 4 * V) { break; } ans = min(ans, max(f[1] - L, 0ll)); while (!Q.empty() && Q.top().r == L) { auto e = Q.top(); Q.pop(); ll nl = e.l * 2; ll nr = e.r * 2 + X; update(e.id, nl); Q.push(Event(nl, nr, e.id)); } } printf("%lld", (ans == oldans) ? ans : 0); return 0; }
Problem C Increment or Xor
可以通過打表發現一個性質:如果前 $2^{n - 1}$ 個數的最高位爲 $0$,序列可以還原當且僅當可以通過一次異或操作還原。
考慮怎麼翻轉某個數的最高位:把低 $n - 1$ 位置爲 $1$,然後執行加操作。
剩下問題變成支持全局加 $1$,全局異或,單點查詢。
把所有數倒過來,用線段樹維護原來倒過來的值爲 $i$ 最終能變成什麼值,全局加 $1$ 相當於對線段樹上 log 個區間打異或標記。
現在回到開頭那個性質。考慮對單位排列進行若干次操作,使得最後最高位不變,現在我們希望證明線段樹上沒有異或標記。
假如原來的值爲 $x$ 的那個位置最高位被翻轉了,那麼這次 $+1$ 操作會在 $x$ 在線段樹上對應的點的所有父節點打異或標記。所以這個位置的最高位在偶數次被翻轉的過程都是對線段樹上同樣的點打標記。因此顯然最終線段樹上是沒有標記的。
Code
#include <bits/stdc++.h> using namespace std; const int Nmx = 1 << 18; int n, N; int a[Nmx]; vector<int> ans; typedef class SegmentTree { public: int n, N, tga, tg[Nmx]; void build(int _n) { n = _n; N = 1 << n; tga = 0; for (int i = 0; i < N; tg[i++] = 0); } void modify(int p, int tga) { if (p >= N) return; int r = 1 ^ (tga & 1) ^ tg[p]; tg[p] ^= 1; modify(p << 1 | r, tga >> 1); } int rev(int x) { int y = 0; for (int i = 0; i < n; i++) { y = y << 1 | (x & 1); x >>= 1; } return y; } int query(int y) { int p = rev(y) + N, v = 1 << n; while (p >>= 1) { v >>= 1; if (tg[p]) y ^= v; } return y ^ tga; } void do_add() { modify(1, tga); } void do_xor(int x) { tga ^= x; } } SegmentTree; SegmentTree st; void do_add() { st.do_add(); ans.push_back(-1); } void do_xor(int x) { if (x) { st.do_xor(x); ans.push_back(x); } } int main() { scanf("%d", &n); N = 1 << n; for (int i = 0; i < N; i++) { scanf("%d", a + i); } int msk = (N - 1) >> 1; st.build(n); for (int i = 0; i < (N >> 1); i++) { int x = st.query(a[i]); if ((x >> (n - 1)) & 1) { do_xor((x & msk) ^ msk); do_add(); } } int x = st.query(a[0]); do_xor(x); for (int i = 0; i < N; i++) { a[i] = st.query(a[i]); if (a[i] != i) { puts("No"); return 0; } } puts("Yes"); printf("%d\n", (signed) ans.size()); for (auto x : ans) { printf("%d ", x); } return 0; }
Problem D Sum Avoidance
真的養老了,這種題也能寫+調 2h
不難發現最多選出 $\left \lfloor \frac{S-1}{2} \right \rfloor$ 個數,顯然全選最大的能滿足這個界。再選 1 個的話會有 2 個數加起來是 $S$。
不難發現最小選出來的數一定是最小的不是 $S$ 的約數的數 $n$,容易發現它很小,不會超過 50.
考慮下一個加進來的數,如果是已有的數能湊出來的,我們不管。所以只用枚舉每個模 $n$ 沒有出現過的餘數,用同餘最短路計算這種餘數最小能放的數是多少。
最後的問題就很傻逼了,所有實際選出來的數是由這不超過 $n$ 個數湊成的,所以直接二分,用同餘最短路算數量。
時間複雜度 $O(T(n^3 + n\log V))$
Code
#include <bits/stdc++.h> using namespace std; #define ll long long template <typename T> void pfill(T* pst, T* ped, T val) { for ( ; pst != ped; *(pst++) = val); } template <typename T> bool vmin(T& a, T b) { return a > b ? (a = b, true) : false; } template <typename T> bool vmax(T& a, T b) { return a < b ? (a = b, true) : false; } const int N = 50; const ll llf = (signed ll) (~0ull >> 2); int T; ll S, mS, K; int n; ll f[N]; bool used[N]; vector<ll> seq; ll calc(ll mid) { ll ret = 0; for (int i = 0; i < n; i++) { if (mid >= f[i]) { ret += (mid - f[i]) / n + 1; } } return ret - 1; } void solve() { scanf("%lld%lld", &S, &K); if (K > ((S - 1) >> 1)) { puts("-1"); return; } for (n = 2; !(S % n); n++); int sr = S % n; seq.clear(); pfill(f, f + n, llf); pfill(used, used + n, false); f[0] = 0; ll nx = n; while (nx < S) { if (nx ^ n) { int w = nx % n; used[w] = true; for (int s = 0, p, q; s < n; s++) { p = s; while (vmin(f[q = (p + w) % n], f[p] + nx)) { p = q; } } } seq.push_back(nx); nx = S; for (int r = 1; r < n; r++) { if (used[r]) continue; ll L = n + r; for (int p = sr, d = 1, q; d <= n; d++, p = q) { q = (p + n - r) % n; vmax(L, (S + 1 - f[q] + d - 1) / d); } vmin(nx, (L - r + n - 1) / n * n + r); } } sort(seq.begin(), seq.end()); /* cerr << "seq:" ; for (auto x : seq) { cerr << x << " "; } cerr << endl; */ ll l = 2, r = S - 1, mid; while (l <= r) { mid = (l + r) >> 1; if (calc(mid) < K) { l = mid + 1; } else { r = mid - 1; } } printf("%lld\n", l); } int main() { scanf("%d", &T); while (T--) { solve(); } return 0; }
Problem E RowCol/ColRow Sort
先考慮只有 01 的情形。
這個排序操作相當於對所有 0 做 2048 裏的往一邊移動的操作。
先列排序再行排序相當於把每列 0 的數量排了個序。先行排序再列排序同理。
所以 0 在原矩陣中每行的數量以及每列的數量都是確定的。
注意到行列之間相鄰關係沒有意義,所以考慮每列以及每列的 0 的數量依次遞減的情形。
不難發現這種情況下 0 的排布是唯一的。證明的話首先可以注意到每列 0 的數量是由每行 0 的數量決定的,第 $i$ 列 0 的數量等於 0 的數量大於等於 $i$ 的行數,所以從最後一列開始填容易證明。
所以這種情形的答案是選取和排布這些行列的方案數。
再回到原問題,每次把 $\leqslant x$ 看作 0,把 $> x$ 的看作 1。現在問題變成把 0 所在的行列塞進 1 構成的階梯形的圖形裏面使得 0 的位置完全在這個階梯形的圖形的內部。
直觀地講(主要是,雖然和最自然的 dp 想法本質相同,但感覺直接講不清楚),一個階梯型可以通過 從下往上填行,從右往左填列(左邊第一列和下面第一行是數量最多的),填一列的時候將它與之前所有行的交點加入階梯型 構造出來。考慮用這個順序在 1 的階梯形裏面填 0 的圖形。考慮填一行的時候會導致一個後綴不能填列,考慮用 dp 數組記錄最強的一個限制,現在的問題變成計算填列的時候的方案數。
你注意到每個列能填的範圍是個前綴,通常情況下,從限制最強的開始計算,每個能選的方案數乘起來即可。雖然這裏順序是反的,但是你知道它對方案數的貢獻係數,直接乘就好了。每日降智(1/1)
時間複雜度 $O(RCV)$
Code
#include <bits/stdc++.h> using namespace std; template <typename T> void pfill(T* pst, T* ped, T val) { for ( ; pst != ped; *(pst++) = val); } #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 bool 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; const int N = 1505; int n, m; int B[N][N]; Zi f[N], g[N], fac[N], _fac[N]; void init_fac(int n) { fac[0] = 1; for (int i = 1; i <= n; i++) { fac[i] = fac[i - 1] * i; } _fac[n] = ~fac[n]; for (int i = n; i; i--) { _fac[i - 1] = _fac[i] * i; } } void get(int val, vector<int>& cr, vector<int>& cc) { cr = vector<int>(n, 0); cc = vector<int>(m, 0); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { cr[i - 1] += (B[i][j] <= val); cc[j - 1] += (B[i][j] <= val); } } sort(cr.begin(), cr.end(), greater<int>()); sort(cc.begin(), cc.end(), greater<int>()); while (!cr.empty() && !cr.back()) cr.pop_back(); while (!cc.empty() && !cc.back()) cc.pop_back(); } vector<int> cr0, cc0, cr1, cc1; Zi work(int lim) { get(lim + 1, cr1, cc1); get(lim, cr0, cc0); if (!cr0[0]) { return 1; } Zi ret = 1; int R = cr1.size(), C = cc1.size(); int R0 = cr0.size(), C0 = cc0.size(); pfill(f, f + R, Zi(1)); int pr = 0, pc = C0 - 1; while (pr < R0 && pc >= 0) { int cntc = 0; while (pr < R0) { if (pr) { for (int i = 0; i < R; i++) { g[i] = f[i]; f[i] = 0; } for (int i = pr; i < R; i++) { f[i] = g[i] * (i - pr + 1); } for (int i = 1; i < R; i++) { g[i] += g[i - 1]; f[i] += g[i - 1]; } } pr++, cntc++; if (pr >= R0 || cr0[pr] != cr0[pr - 1]) { break; } } ret *= _fac[cntc]; cntc = 0; while (pc < C0) { for (int i = 0; i < R; i++) { int sec = cr1[i] - pc; f[i] *= max(sec, 0); } pc--, cntc++; if (pc < 0 || cc0[pc] != cc0[pc + 1]) { break; } } ret *= _fac[cntc]; } Zi sum = 0; for (int i = 0; i < R; i++) { sum += f[i]; } ret *= sum; // cerr << "lim: " << lim << " " << ret.v << '\n'; return ret; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { scanf("%d", B[i] + j); } } init_fac(max(n, m) + 3); Zi ans = 1; for (int x = 8; ~x; x--) { ans *= work(x); } printf("%d\n", ans.v); return 0; }
Problem F Reflection
首先容易觀察到下面幾條性質:
- 操作某個固定次數,三元組 $\{a, b, c\}(a \leqslant b \leqslant c)$ 的差值 $(b - a, c - b)$ 無視順序的話是唯一的
- 每次操作相當於對中間的數加或減差值的最小值
- 兩個不同三元組通過一次操作變成相同的三元組當且僅當它們的差值在不考慮順序下是相同的且中間的數相同。
手玩容易發現:
- 如果某個三元組中間值爲 $x$,進行一次右翻左操作,接下來一直進行左翻右操作可以使得中間值在某次操作後爲 $x$
- 兩個三元組中間值分別爲 $x_1, x_2(x_1 < x_2)$,設 1 次操作後的中間值爲 $y_1, y_2$,那麼有 $y_1 \leqslant y_2$
前一條性質的證明考慮差值(不考慮順序)爲 $(x, y) (x <y)$ 的情形,分 $y \geqslant 2x$ 和 $x < y < 2x$ 兩種情況分別討論一下就能證明。
後一條可能不太顯然。考慮當差值(不考慮順序)爲 $(x, y) (x < y)$ 的所有三元組,把所有不同的中間值從小到大排序,考慮相鄰兩個的差,進行一次操作相當於把這中間每個數減去 $2x$,然後在兩兩之間以及兩端插入 $2x$,接着把所有 $0$ 剔除掉。考慮一個這中間某個差相當於是做了一次操作,然後下面一直往反方向操作,由前一條性質能保證這個至多會被減爲 $0$。
畫一個這樣的圖:對於每個差值(不考慮順序)有一個數軸,把能到的三元組的中間值在上面標出來,然後一個三元組向它能直接到的三元組連邊。
容易發現某個三元組 $\{a, b, c\}$ 能直接到的兩個三元組 $A, B$,$A, B$ 所有能到的三元組的交集爲第一次中間值回到 $b$ 三元組的所有能到的三元組。證明比較顯然。
注意到 $A, B$ 的“子樹”其實長得一模一樣。把重複部分扣掉就獨立了。所以就可以 dp 啦。直接做複雜度不太行,但是對於最小值相同的情形,轉移相當於一個數列遞推,套一個矩陣快速冪去優化一下就可以了。
剩下就是處理一堆無聊的細節:
- 差值裏有個 $0$ 的一箇中間值對應的三語組數量都是 $2$
- 差值中的兩個數相等,對應的三元組數量都是 $1$
- (......)
Code
#include <bits/stdc++.h> using namespace std; template <typename T> void pfill(T* pst, T* ped, T val) { for ( ; pst != ped; *(pst++) = val); } #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 bool 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; typedef class Matrix { public: int r, c; Zi a[3][3]; Matrix(int r = 0, int c = 0) : r(r), c(c) { memset(a, 0, sizeof(a)); } Zi* operator [] (int p) { return a[p]; } Matrix operator * (Matrix b) { Matrix rt (r, b.c); for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { for (int k = 0; k < b.c; k++) { rt[i][k] += a[i][j] * b[j][k]; } } } return rt; } } Matrix; Matrix tr (3, 3); Matrix qpow(Matrix a, ll p) { Matrix rt(3, 3); for (int i = 0; i < rt.r; i++) { rt[i][i] = 1; } for ( ; p; p >>= 1, a = a * a) { if (p & 1) { rt = rt * a; } } return rt; } Zi dp(ll a, ll b, Zi& f_1, Zi& f_2) { if (!(a % b)) { ll n = a / b; Matrix f (1, 3); f[0][0] = 9, f[0][1] = 6, f[0][2] = 1; f = f * qpow(tr, n - 2); f_1 = 3, f_2 = f[0][1]; return f[0][0]; } ll n = a / b; Matrix f (1, 3); Zi of; of = f[0][0] = dp(b, a % b, f[0][1], f_1); f[0][2] = 1; f = f * qpow(tr, n); f_2 = ((n == 1) ? of : f[0][1]); // cerr << a << " " << b << " " << f[0][0].v << endl; return f[0][0]; } void solve() { ll x, y, z, a, b; scanf("%lld%lld%lld", &x, &y, &z); a = y - x, b = z - y; if (a < b) swap(a, b); if (!b) { puts(a == b ? "1" : "2"); return; } if (a == b) { puts("5"); return; } Zi ans, foo, bar; ans = dp(a, b, foo, bar); printf("%d\n", ans.v); } int T; int main() { tr[0][0] = 2, tr[0][1] = 1; tr[1][0] = Mod - 1; tr[2][0] = 2, tr[2][2] = 1; scanf("%d", &T); while (T--) { solve(); } return 0; }