【CodeForces】CodeForces Round #463 (Div. 1 + Div. 2) 題解
阿新 • • 發佈:2018-12-24
【比賽連結】
【題解連結】
【A】Palindromic Supersequence
【思路要點】
- 將字串正反各列印一遍。
- 時間複雜度\(O(|A|)\)。
【程式碼】
#include<bits/stdc++.h> using namespace std; const int MAXN = 5005; template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } char s[MAXN]; int main() { scanf("%s", s + 1); int len = strlen(s + 1); printf("%s", s + 1); reverse(s + 1, s + len + 1); printf("%s\n", s + 1); return 0; }
【B】Recursive Queries
【思路要點】
- 暴力計算\([1,10^6]\)內所有整數的\(f\)和\(g\)函式的值。
- 處理字首和陣列,\(O(1)\)回答詢問。
- 時間複雜度\(O(rk+Q)\)。
【程式碼】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1000005; const int CNT = 10; template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int f[MAXN], g[MAXN]; int sum[MAXN][CNT]; int main() { for (int i = 1; i < MAXN; i++) { int tmp = i, now = 1; while (tmp) { if (tmp % 10) now *= tmp % 10; tmp /= 10; } f[i] = now; if (i <= 9) g[i] = i; else g[i] = g[f[i]]; for (int j = 1; j < CNT; j++) sum[i][j] = sum[i - 1][j]; sum[i][g[i]]++; } int q; read(q); while (q--) { int l, r, k; read(l), read(r), read(k); writeln(sum[r][k] - sum[l - 1][k]); } return 0; }
【C】Permutation Cycle
【思路要點】
- 將排列的每一位\(A_i\)看做點\(i\)向\(A_i\)連出的一條邊,那麼整個圖由若干個簡單環組成,\(g(i)\)表示\(i\)所在的環的長度。
- 列舉得到一組\(Ax+By=N\)的非負整數解,再構造排列即可。
- 時間複雜度\(O(N)\)。
【程式碼】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1000005; template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int ans[MAXN]; int main() { int n, a, b; read(n), read(a), read(b); for (int i = 0; i <= n; i += a) { if ((n - i) % b) continue; for (int j = 1; j <= i; j += a) { for (int k = j; k <= j + a - 2; k++) printf("%d ", k + 1); printf("%d ", j); } for (int j = i + 1; j <= n; j += b) { for (int k = j; k <= j + b - 2; k++) printf("%d ", k + 1); printf("%d ", j); } printf("\n"); return 0; } printf("-1\n"); return 0; }
【D】Tree
【思路要點】
- 可以發現,在詢問2中規定的序列中,一個元素\(i\)的後繼若存在,是唯一的,只有可能是該元素在原樹上離它最近的權值大於等於該元素權值的祖先。
- 這則資訊我們可以通過倍增在\(O(LogQ)\)的時間內找到。
- 這樣的祖先-後代關係形成了一個森林,在其上再次倍增即可回答詢問。
- 時間複雜度\(O(QLogQ)\)。
【程式碼】
#include<bits/stdc++.h> using namespace std; const int MAXN = 400005; const int MAXLOG = 20; template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int weight[MAXN]; int gather[MAXN][MAXLOG]; long long sum[MAXN][MAXLOG]; int father[MAXN][MAXLOG], Max[MAXN][MAXLOG]; int main() { int n, cnt = 1; read(n); long long lastans = 0; for (int q = 1; q <= n; q++) { int opt; long long e, f; read(opt), read(e), read(f); e ^= lastans; f ^= lastans; if (opt == 1) { father[++cnt][0] = e; sum[cnt][0] = weight[cnt] = Max[cnt][0] = f; for (int i = 1; i < MAXLOG; i++) { father[cnt][i] = father[father[cnt][i - 1]][i - 1]; Max[cnt][i] = max(Max[cnt][i - 1], Max[father[cnt][i - 1]][i - 1]); } int now = father[cnt][0]; for (int i = MAXLOG - 1; i >= 0; i--) if (Max[now][i] < weight[cnt]) now = father[now][i]; gather[cnt][0] = now; for (int i = 1; i < MAXLOG; i++) { gather[cnt][i] = gather[gather[cnt][i - 1]][i - 1]; sum[cnt][i] = sum[cnt][i - 1] + sum[gather[cnt][i - 1]][i - 1]; } } else { int ans = 0, now = e; for (int i = MAXLOG - 1; i >= 0; i--) if (gather[now][i] != 0 && f >= sum[now][i]) { f -= sum[now][i]; now = gather[now][i]; ans += 1 << i; } if (f >= sum[now][0]) ans++; writeln(lastans = ans); } } return 0; }
【E】Team Work
【思路要點】
- 首先:$$Ans=\sum_{i=1}^{N}\binom{N}{i}*i^k$$
- 學過高等代數的人可以像題解一樣,對\((x+1)^N=\sum_{i=1}^{N}\binom{N}{i}*x^i\)多次求導,得到答案式。
- 顯然這很不OI,我們應當選用更加
優雅OI的做法。- 賦予上式組合意義:先在\(N\)個人中選取一個非空子集,再在選取的人中重複選取\(k\)個人,組成一個有序的可重陣列的方案數。
- 該表達方式等價於:先在全部的\(N\)個人中重複選取\(k\)個人,組成一個有序的可重陣列,再選出一個\(N\)個人的非空子集,使得它包含了選出的全部\(k\)個人。
- 也就是說,如果令\(F_i\)表示選出的\(k\)個人中,有\(i\)個不同的人的方案數,那麼$$Ans=\sum_{i=1}^{min(N,k)}F_i*2^{N-i}$$
- \(F_{i}\)可以通過簡單的DP得到。
- 時間複雜度\(O(k^2)\)。
- 或者,我們也可以先
找一找規律觀察一下小資料的性質。- 我們發現:
- 當\(k=0\),\(Ans=2^N\)。
- 當\(k=1\),\(Ans=N*2^{N-1}\)。
- 當\(k=2\),\(Ans=N*(N+1)*2^{N-2}\)。
- 當\(k=3\),\(Ans=N^2*(N+3)*2^{N-3}\)。
- 顯然,沒有直觀的答案的規律,但是我們發現,如果將答案除去\(2^N\),剩下的部分是一個\(k\)次的多項式。
- 因此,我們暴力計算\(N\)較小時的答案,將其除去\(2^N\),再進行拉格朗日插值,求得上述多項式,回答詢問。
- 時間複雜度\(O(k^2)\)。
【程式碼】
/*DP Version*/ #include<bits/stdc++.h> using namespace std; const int MAXN = 5005; const int P = 1e9 + 7; template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } long long power(long long x, long long y) { if (y == 0) return 1; long long tmp = power(x, y / 2); if (y % 2 == 0) return tmp * tmp % P; else return tmp * tmp % P * x % P; } long long dp[MAXN][MAXN]; int main() { int n, k; read(n), read(k); dp[0][0] = 1; for (int i = 0; i <= k - 1; i++) for (int j = 0; j <= i; j++) { dp[i + 1][j] = (dp[i + 1][j] + dp[i][j] * j) % P; dp[i + 1][j + 1] = (dp[i + 1][j + 1] + dp[i][j] * (n - j)) % P; } long long ans = 0; for (int i = 1; i <= k; i++) ans = (ans + dp[k][i] * power(2, n - i)) % P; writeln(ans); return 0; } /*Lagrange Version*/ #include<bits/stdc++.h> using namespace std; const int MAXN = 5009; const int P = 1e9 + 7; template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } long long power(long long x, long long y) { if (y == 0) return 1; long long tmp = power(x, y / 2); if (y % 2 == 0) return tmp * tmp % P; else return tmp * tmp % P * x % P; } namespace Lagrange { int n; long long x[MAXN], y[MAXN], a[MAXN]; long long p[MAXN], q[MAXN]; void work() { memset(p, 0, sizeof(p)); p[0] = 1; for (int i = 1; i <= n; i++) { for (int j = i - 1; j >= 0; j--) { p[j + 1] = (p[j + 1] + p[j]) % P; p[j] = (P - p[j] * x[i] % P) % P; } } for (int i = 1; i <= n; i++) { memset(q, 0, sizeof(q)); for (int j = n - 1; j >= 0; j--) q[j] = (p[j + 1] + q[j + 1] * x[i]) % P; long long now = 1; for (int j = 1; j <= n; j++) if (j != i) now = now * (x[i] - x[j]) % P; now = power((P + now) % P, P - 2); for (int j = 0; j <= n; j++) q[j] = q[j] * now % P; for (int j = 0; j <= n; j++) a[j] = (a[j] + q[j] * y[i]) % P; } } long long get(long long x) { long long ans = 0, now = 1; for (int i = 0; i <= n; i++) { ans = (ans + now * a[i]) % P; now = now * x % P; } return ans; } } long long mul[MAXN], fac[MAXN], inv[MAXN]; long long c(int x, int y) {return fac[x] * inv[y] % P * inv[x - y] % P; } int main() { int n, k; read(n), read(k); for (int i = 1; i < MAXN; i++) mul[i] = power(i, k); fac[0] = 1; for (int i = 1; i < MAXN; i++) fac[i] = fac[i - 1] * i % P; inv[MAXN - 1] = power(fac[MAXN - 1], P - 2); for (int i = MAXN - 2; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % P; long long two = 1, owt = power(2, P - 2); for (int i = 1; i <= k + 5; i++) { long long now = 0; for (int j = 1; j <= i; j++) now += c(i, j) * mul[j] % P; now %= P; two = two * owt % P; using namespace Lagrange; Lagrange::n++, x[Lagrange::n] = i, y[Lagrange::n] = now * two % P; } Lagrange::work(); writeln(Lagrange::get(n) * power(2, n) % P); return 0; }
【F】Escape Through Leaf
【思路要點】
- 顯然有DP:$$F_i=min_{j\in i's\ subtree}\{F_j+A_iB_j\}$$
- 設\(B_k>B_j\),考慮決策\(k\)優於決策\(j\)的充要條件,應當是:
$$F_k+A_iB_k≤F_j+A_iB_j$$
$$\Leftrightarrow -A_i≥\frac{F_k-F_j}{B_k-B_j}$$- 因此,我們需要維護一個將每個點的\((B_i,F_i)\)當做座標的下凸殼。
- 手寫平衡樹或者使用std::multiset均可。
- 由於是樹上的問題,我們還需要啟發式合併平衡樹來維護決策凸殼。
- 時間複雜度\(O(NLog^2N)\)。
【程式碼】
#include<bits/stdc++.h> using namespace std; const int MAXN = 100005; const long double INF = 1e99; template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct point {long long x, y; }; point operator + (point a, point b) {return (point) {a.x + b.x, a.y + b.y}; } point operator - (point a, point b) {return (point) {a.x - b.x, a.y - b.y}; } long long operator * (point a, point b) {return a.x * b.y - a.y * b.x; } bool operator < (point a, point b) { if (a.x == b.x) return a.y < b.y; else return a.x < b.x; } struct line {point pos; mutable long double k; }; bool mode = false; bool operator < (line a, line b) { if (mode) return a.k < b.k; else return a.pos < b.pos; } struct Tree : multiset<line> { void init() {clear(); } long double div(long double a, long double b) {return a / b; } bool update(iterator x, iterator y) { //Update *x.k && check and return if y needs to be erased. if (y == end()) {(*x).k = INF; return false;} if ((*x).pos.x == (*y).pos.x) {erase(y); return true; }; (*x).k = div((*y).pos.y - (*x).pos.y, (*y).pos.x - (*x).pos.x); if ((*x).k >= (*y).k) {erase(y); return true; } else return false; } void ins(point x) { iterator p = insert((line) {x, 0}), q, r; for (q = p, q++; update(p, q); q = p, q++); if (p == begin()) return; q = p, q--; if (update(q, p)) {p = q, p++, update(q, p); return; }; for (q = p, q--, r = q; q != begin() && update(--r, q) && p != begin(); q = p, q--, r = q) update(r, p); } long long query(long long x) { mode = true; iterator tmp = lower_bound((line) {(point) {0, 0}, (long double) (-x)}); mode = false; return (*tmp).pos.y + x * (*tmp).pos.x; } }; vector <int> e[MAXN]; long long a[MAXN], b[MAXN], f[MAXN]; int size[MAXN], son[MAXN]; void dfs(int pos, int fa) { size[pos] = 1; for (unsigned i = 0; i < e[pos].size(); i++) if (e[pos][i] != fa) { dfs(e[pos][i], pos); size[pos] += size[e[pos][i]]; if (size[e[pos][i]] > size[son[pos]]) son[pos] = e[pos][i]; } } void work(int pos, int fa, Tree &t) { if (fa != 0 && e[pos].size() == 1) { f[pos] = 0; t.ins((point) {b[pos], f[pos]}); return; } work(son[pos], pos, t); for (unsigned i = 0; i < e[pos].size(); i++) { if (e[pos][i] == fa || e[pos][i] == son[pos]) continue; Tree tmp; tmp.init(); work(e[pos][i], pos, tmp); for (Tree :: iterator j = tmp.begin(); j != tmp.end(); j++) t.ins((*j).pos); } f[pos] = t.query(a[pos]); t.ins((point) {b[pos], f[pos]}); } int main() { int n; read(n); for (int i = 1; i <= n; i++) read(a[i]); for (int i = 1; i <= n; i++) read(b[i]); for (int i = 1; i <= n - 1; i++) { int x, y; read(x), read(y); e[x].push_back(y); e[y].push_back(x); } dfs(1, 0); Tree tmp; tmp.init(); work(1, 0, tmp); for (int i = 1; i <= n; i++) printf("%I64d ", f[i]); return 0; }
【G】Palindrome Partition
【思路要點】
- 令\(N=|S|\),考慮字串\(T=S_1S_NS_2S_{N-1}S_3S_{N-2}...S_{\frac{N}{2}}S_{\frac{N}{2}+1}\)。
- 問題等價於將\(T\)劃分為若干長度為偶數的迴文子串的方案數。
- 用一種奇怪的迴文樹優化DP可以解決這個問題。
- 時間複雜度\(O(NLogN)\)。
【程式碼】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e6 + 5; const int P = 1e9 + 7; const int MAXC = 26; template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct PalindromicTree { int root, size, last, n, m; int f[MAXN], g[MAXN]; int up[MAXN], delta[MAXN]; int father[MAXN], depth[MAXN]; int child[MAXN][MAXC]; char s[MAXN]; int newnode(int len) { depth[size] = len; return size++; } void extend(int ch) { int p = last; ++m; while (s[m] != s[m - depth[p] - 1]) p = father[p]; if (!child[p][ch]) { int np = newnode(depth[p] + 2); child[p][ch] = np; if (p == root) father[np] = 1; else { int q = father[p]; while (s[m] != s[m - depth[q] - 1]) q = father[q]; if (child[q][ch] == root) father[np] = 1; else father[np] = child[q][ch]; } delta[np] = depth[np] - depth[father[np]]; up[np] = (delta[np] == delta[father[np]]) ? up[father[np]] : father[np]; } last = child[p][ch]; } void calc(char *t) { n = strlen(t + 1); m = 0; int l = 1, r = n; for (int i = 1; i <= n; i++) if (i & 1) s[i] = t[l++]; else s[i] = t[r--]; f[0] = 1; root = size = 0; root = newnode(-1); last = newnode(0); father[last] = root; for (int i = 1; i <= n; i++) { extend(s[i] - 'a'); for (int j = last; depth[j] > 0; j = up[j]) { if (father[j] == up[j]) g[j] = f[i - depth[j]]; else g[j] = (g[father[j]] + f[i - delta[j] - depth[up[j]]]) % P; if (i % 2 == 0) f[i] = (f[i] + g[j]) % P; } } writeln(f[n]); } } PT; char s[MAXN]; int main() { scanf("%s", s + 1); PT.calc(s); return 0; }