從這裏開始
Problem A Dividing a String
猜想每段長度不超過2。然後dp即可。
考慮最後一個長度大於等於3的一段,如果劃成$1 + 2$會和後面相同,那麼劃成$2 + 1$,如果前一段和前面相同,那麼把前一段和前面合併。每次操作後段數都不會減少。所以存在一種最優方案使得每段長度不超過2。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 2e5 + 5; template <typename T> void vmax(T& a, T b) { (a < b) && (a = b, 0); } int n; char s[N]; int f[N][2]; int main() { scanf("%s", s + 1); f[0][0] = 0, f[0][1] = -1e9; n = strlen(s + 1); for (int i = 1; i <= n; i++) { f[i][0] = f[i][1] = -1e9; vmax(f[i][0], f[i - 1][1] + 1); if (s[i] != s[i - 1]) vmax(f[i][0], f[i - 1][0] + 1); if (i > 1) { vmax(f[i][1], f[i - 2][0] + 1); if (i > 3 && (s[i] != s[i - 2] || s[i - 1] != s[i - 3])) { vmax(f[i][1], f[i - 2][1] + 1); } } } printf("%d\n", max(f[n][0], f[n][1])); return 0; }
Problem B RGB Balls
假設紅綠藍三種顏色的求按順序排列後分別爲 $r_1, r_2, \cdots, r_n, g_1, g_2, \cdots, g_n, b_1, b_2, \cdots, b_n$。
設$m_i = \min\{r_i, b_i, g_i\}, M_i = \max\{r_i, b_i, g_i\}$。猜想答案是$\sum M_i - m_i$。
假設每個人拿到的最小標號的球標號遞增,證明考慮前$k$個人至多選擇前$k$個紅球,白球和藍球,所以第$k + 1$個人拿到的最小標號的球的標號至少爲$m_{k + 1}$,所以$m_i$是第$i$個人拿到的最小標號的球的上界。同理可以證明第$i$個人拿到的最大標號的球的下界是$M_i$。
然後根據一種球的類型來貪心就行了。
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; const int N = 1e5 + 5; int n; char s[N * 3]; int main() { scanf("%d", &n); scanf("%s", s + 1); int r, g, b, rg, rb, gb; r = g = b = 0; rg = rb = gb = 0; Zi ans = 1; for (int i = 1; i <= n; i++) ans *= i; for (int i = 1; i <= 3 * n; i++) { if (s[i] == 'R') { if (gb) { ans *= gb--; } else if (g) { ans *= g--; rg++; } else if (b) { ans *= b--; rb++; } else { r++; } } else if (s[i] == 'G') { if (rb) { ans *= rb--; } else if (r) { ans *= r--; rg++; } else if (b) { ans *= b--; gb++; } else { g++; } } else if (s[i] == 'B') { if (rg) { ans *= rg--; } else if (r) { ans *= r--; rb++; } else if (g) { ans *= g--; gb++; } else { b++; } } } printf("%d\n", ans.v); return 0; }
Problem C Numbers on a Circle
考慮倒着做,操作變成一個數減去兩邊的和,如果一個數可以操作,那麼它兩邊的數都不能操作。所以要麼它達到它目標的值,要麼它比兩邊的數小。
不難發現操作1次,要麼折半,要麼達到目標的值,要麼判出無解,所以總時間複雜度$O(n\log V)$。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 2e5 + 5; int n; int A[N]; int B[N]; boolean inq[N]; boolean check(int p) { return B[p] >= B[(p + n - 1) % n] + B[(p + 1) % n]; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", A + i); } for (int i = 0; i < n; i++) { scanf("%d", B + i); } queue<int> Q; for (int i = 0; i < n; i++) { if (check(i)) { inq[i] = true; Q.push(i); } } long long ans = 0; while (!Q.empty()) { int p = Q.front(); Q.pop(); inq[p] = false; int pre = (p + n - 1) % n, suf = (p + 1) % n; int sum = B[pre] + B[suf]; int t = max(0, (B[p] - A[p]) / sum); if (!t && B[p] != A[p]) { puts("-1"); return 0; } ans += t; B[p] -= sum * t; if (!inq[pre] && check(pre)) { inq[pre] = true; Q.push(pre); } if (!inq[suf] && check(suf)) { inq[suf] = true; Q.push(suf); } } for (int i = 0; i < n; i++) { if (A[i] ^ B[i]) { puts("-1"); return 0; } } printf("%lld\n", ans); return 0; }
Problem D Sorting a Grid
考慮第一次移動需要達到的條件:屬於目標同一行的數不在同一列。
問題相當於給這樣一個圖染色:有$nm$個點,如果$(i,j)$和$(x, y)$有邊相鄰當且僅當它們屬於同一行或者屬於目標同一行。
它所在的顏色標號等於它被換到的列號。
考慮每次標出一種顏色。這個可以轉成匹配問題,把原圖的每個點看成邊,兩端點分別是它所在的兩個團。
這是一個正則二分圖,所以必定有解。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int inf = (signed) (~0u >> 1); typedef class Edge { public: int ed, nx, r; Edge() { } Edge(int ed, int nx, int r) : ed(ed), nx(nx), r(r) { } } Edge; typedef class MapManager { public: int *h; vector<Edge> E; MapManager() { } MapManager(int n) { h = new int[(n + 1)]; memset(h, -1, sizeof(int) * (n + 1)); } ~MapManager() { delete[] h; E.clear(); } void add_edge(int u, int v, int r) { E.emplace_back(v, h[u], r); h[u] = (signed) E.size() - 1; } void add_arc(int u, int v, int r) { add_edge(u, v, r); add_edge(v, u, 0); } Edge& operator [] (int p) { return E[p]; } } MapManager; typedef class Network { public: int S, T; MapManager g; int *d, *h; Network(int S, int T) : S(S), T(T), g(T) { d = new int[(T + 1)]; h = new int[(T + 1)]; } ~Network() { delete[] d; delete[] h; } boolean bfs() { queue<int> Q; memset(d, -1, sizeof(int) * (T + 1)); d[S] = 0; Q.push(S); while (!Q.empty()) { int e = Q.front(); Q.pop(); for (int i = g.h[e], eu; ~i; i = g[i].nx) { eu = g[i].ed; if (!g[i].r || ~d[eu]) continue; d[eu] = d[e] + 1; Q.push(eu); } } return d[T] != -1; } int dfs(int p, int mf) { if (p == T || !mf) return mf; int flow = 0, f; for (int& i = h[p], j, e; ~i; (i != -1) && (i = g[i].nx)) { e = g[i].ed, j = i; if (g[i].r && d[e] == d[p] + 1 && (f = dfs(e, min(mf, g[i].r))) > 0) { g[j].r -= f; g[j ^ 1].r += f; flow += f; mf -= f; if (!mf) break; } } return flow; } int dinic() { int rt = 0; while (bfs()) { for (int i = 0; i <= T; i++) h[i] = g.h[i]; rt += dfs(S, inf); } return rt; } void add_edge(int u, int v, int r) { g.add_arc(u, v, r); } } Network; const int N = 105; int n, m; int a[N][N]; int b[N][N]; int c[N][N]; int id[N][N]; int col[N][N]; int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { scanf("%d", a[i] + j); } } for (int c = 1, T; c <= m; c++) { Network network (0, T = n + n + 1); for (int i = 1; i <= n; i++) network.add_edge(0, i, 1); for (int i = 1; i <= n; i++) network.add_edge(i + n, T, 1); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (!col[i][j]) { network.add_edge(i, (a[i][j] + m - 1) / m + n, 1); id[i][j] = (signed) network.g.E.size() - 1; } } } network.dinic(); MapManager& g = network.g; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (!col[i][j] && g[id[i][j]].r) { col[i][j] = c; } } } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { b[i][col[i][j]] = a[i][j]; } } for (int i = 1; i <= n; i++, putchar('\n')) { for (int j = 1; j <= m; j++) { printf("%d ", b[i][j]); c[(b[i][j] + m - 1) / m][j] = b[i][j]; } } for (int i = 1; i <= n; i++, putchar('\n')) { for (int j = 1; j <= m; j++) { printf("%d ", c[i][j]); } } return 0; }
Problem E Reversing and Concatenating
假設最小的字符爲a,如果末尾有$a$,那麼可以是$a$的數量變爲之前的$2^{K}$倍,否則要先用一次操作使得它在末尾。
你發現使得$a$的長度達到這個長,方案是唯一的。
你枚舉開始可能的串,這個至多有$O(n)$個。直接計算就行了。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 5005; int n, K; int mxR[N]; char s[N << 1], t[N], ans[N]; void check(char* s, int k, char min_c) { int L = 0, p = n; while (s[n - L] == min_c) L++, p--; L <<= k; if (L >= n) { for (int i = 1; i <= n; i++) putchar(min_c); putchar('\n'); exit(0); } for (int i = 1; i <= L; i++) { t[i] = min_c; } for (int i = L + 1; i <= n; i++) t[i] = s[p--]; for (int i = 1; i <= n; i++) if (t[i] ^ ans[i]) { if (t[i] > ans[i]) { return; } break; } for (int i = 1; i <= n; i++) ans[i] = t[i]; } int main() { scanf("%d%d", &n, &K); scanf("%s", s + 1); for (int i = 1; i <= n; i++) s[2 * n - i + 1] = s[i]; char x = 'z', y = 'a'; for (int i = 1; i <= n; i++) { x = min(x, s[i]); y = max(y, s[i]); } if (K >= 20 || x == y) { for (int i = 1; i <= n; i++) putchar(x); putchar('\n'); return 0; } ans[1] = 'z' + 1; if (s[n] == x) check(s, K, x); int mxL = 0; for (int i = 1, j = 1; i <= n; i = j) { if (s[i] != x) { j++; continue; } while (s[j] == s[i]) j++; mxR[i] = j - i; mxL = max(mxL, mxR[i]); } for (int i = 1; i <= n; i++) { if (mxR[i] == mxL) { check(s + (n - i + 1), K - 1, x); } } puts(ans + 1); return 0; }
Problem F Counting of Subarrays
可以先把問題轉化成,你可以選擇至少$l$個數,將它們合成一個數,問有多少個區間能合成一個數。
考慮如何判斷一個區間是否可行:
- 取最小的元素$x$的連續段,假設長度爲$L$,那麼至多可以合成$\lfloor L / l \rfloor$個$x + 1$。
- 遞歸執行。
考慮怎麼計算數量,考慮一個區間在它被合成一個可能的最小的數的時候計算。
假設當前序列中最小的數位$x$,每次計算能合成的最小數爲$x + 1$的區間個數,然後把$x$的連續段縮成若干個$x + 1$。
要計算合成的數位$x + 1$的區間個數,只用求選擇的連續至少$l$或者$1$個$x$的方案數。
縮數後爲了保證不算重,相當於要求每次選擇的區間不能被這個連續段包含。把被包含的方案數減去。然後計算這個連續段內產生$k$個$x + 1$的可能的左端點數和右端點數。
時間複雜度$O(n\log n)$
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 2e5 + 5; #define pii pair<int, int> #define ll long long typedef class Segment { public: int l, r, x, y; Segment() { } Segment(int l, int r, int x, int y) : l(l), r(r), x(x), y(y) { } boolean operator < (Segment b) const { return l < b.l; } } Segment; int n, L; int a[N]; pii b[N]; ll calc(vector<pii>& a) { ll ret = 0, sum = 0; for (int l = 0, r = L - 1; r < (signed) a.size(); l++, r++) { ret += (sum += a[l].first) * a[r].second; } return ret; } ll ans = 0; int main() { scanf("%d%d", &n, &L); for (int i = 1; i <= n; i++) { scanf("%d", a + i); b[i] = pii(a[i], i); } ans = n; sort(b + 1, b + n + 1); int pos = 1, val; vector<Segment> vcur, vnxt, vadd; while (true) { if (!vcur.size()) { if (pos > n) { break; } else { val = b[pos].first; } } else { val++; } // cerr << pos << " " << val << '\n'; vadd.clear(); while (pos <= n && b[pos].first == val) vadd.emplace_back(b[pos].second, b[pos].second, 1, 1), pos++; vnxt.resize(vcur.size() + vadd.size()); merge(vcur.begin(), vcur.end(), vadd.begin(), vadd.end(), vnxt.begin()); swap(vcur, vnxt); vnxt.clear(); int num = vcur.size(); for (int i = 0, j = 0; i < num; i = ++j) { while (j < num - 1 && vcur[j].r + 1 == vcur[j + 1].l) j++; int len = j - i + 1, cnt = len / L; if (cnt) { vector<pii> tmp; for (int k = i; k <= j; k++) tmp.emplace_back(vcur[k].x, vcur[k].y); ans += calc(tmp); tmp.clear(); tmp.resize(cnt, pii(0, 0)); for (int k = L - 1; k < len; k++) { tmp[cnt - (k - L + 1) / L - 1].first += vcur[j - k].x; } for (int k = L - 1; k < len; k++) { tmp[(k - L + 1) / L].second += vcur[i + k].y; } ans -= calc(tmp); for (int k = 0; k < cnt; k++) vnxt.emplace_back(vcur[i].l + k, vcur[i].l + k, tmp[k].first, tmp[k].second); vnxt.back().r = vcur[j].r; } } swap(vcur, vnxt); vnxt.clear(); } printf("%lld\n", ans); return 0; }