【比赛链接】
- 点击打开链接
【题解链接】
- 点击打开链接
【A】Palindromic Supersequence
【思路要点】
- 将字符串正反各打印一遍。
- 时间复杂度\(O(|A|)\)。
【代码】
#include
using namespace std; const int MAXN = 5005; template 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 void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template 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
using namespace std; const int MAXN = 1000005; const int CNT = 10; template 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 void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template 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
using namespace std; const int MAXN = 1000005; template 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 void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template 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
using namespace std; const int MAXN = 400005; const int MAXLOG = 20; template 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 void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template 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
using namespace std; const int MAXN = 5005; const int P = 1e9 + 7; template 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 void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template 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 using namespace std; const int MAXN = 5009; const int P = 1e9 + 7; template 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 void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template 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
using namespace std; const int MAXN = 100005; const long double INF = 1e99; template 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 void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template 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 { 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 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
using namespace std; const int MAXN = 1e6 + 5; const int P = 1e9 + 7; const int MAXC = 26; template 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 void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template 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; }