【比赛链接】
- 点击打开连接
【题解链接】
- 点击打开链接
**【A】**Parity
【思路要点】
- 分 b b b 的奇偶性讨论即可。
- 时间复杂度 O ( k ) O(k) O(k) 。
【代码】
#include
using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 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 main() { int k, n, x; read(k), read(n); bool ans = false; if (k & 1) { for (int i = 1; i <= n; i++) { read(x); ans ^= x % 2 == 1; } } else { for (int i = 1; i <= n; i++) read(x); ans = x % 2 == 1; } if (ans) puts("odd"); else puts("even"); return 0; }
**【B】**Tape
【思路要点】
- 在全部的 N − 1 N-1 N−1 段间断位置中,选择最短的 N − k N-k N−k 段覆盖。
- 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN) 。
【代码】
#include
using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 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 n, m, k, a[MAXN], b[MAXN]; int main() { read(n), read(m), read(k); for (int i = 1; i <= n; i++) read(a[i]); for (int i = 2; i <= n; i++) b[i] = a[i] - a[i - 1] - 1; sort(b + 1, b + n + 1); int ans = a[n] - a[1] + 1; while (k >= 2) { ans -= b[n]; n--, k--; } writeln(ans); return 0; }
**【C】**Meaningless Operations
【思路要点】
- 记 f ( x ) f(x) f(x) 表示不小于 x x x 的,二进制表示每一位都是 1 1 1 的数。
- 若 f ( a ) ≠ a f(a)\ne a f(a)̸=a ,取 b = f ( a ) ⊕ a b=f(a)\oplus a b=f(a)⊕a 即可将答案取至上界 f ( a ) f(a) f(a) 。
- 否则, g c d ( a ⊕ b , a & b ) = g c d ( a − b , b ) gcd(a\oplus b,a\&b)=gcd(a-b,b) gcd(a⊕b,a&b)=gcd(a−b,b) ,问题即找到 a a a 最大的因数。
- 时间复杂度 O ( Q A ) O(Q\sqrt{A}) O(QA) 。
【代码】
#include
using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 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 main() { int q; read(q); while (q--) { int n; read(n); int tmp = 1; while (tmp < n) { tmp <<= 1; tmp += 1; } if (tmp == n) { int ans = 1; for (int i = 2; i * i <= n; i++) if (n % i == 0) { ans = n / i; break; } writeln(ans); } else writeln(tmp); } return 0; }
**【D】**Jongmah
【思路要点】
- 可以发现,相同的三个顺子同样可以表示为三个刻子,因此我们不妨假设相同的顺子至多存在 2 2 2 个。
- 这样,我们就可以设计动态规划了,记 d p i , j , k dp_{i,j,k} dpi,j,k 表示考虑了所有以 1 , 2 , . . . , i 1,2,...,i 1,2,...,i 开头的顺子和这些牌对应的刻子,且 i − 1 i-1 i−1 开头的顺子有 j ( j ≤ 2 ) j\ (j≤2) j (j≤2) 个, i i i 开头的顺子有 k ( k ≤ 2 ) k\ (k≤2) k (k≤2) 个的情况下,最多的牌型总数,转移时枚举 i + 1 i+1 i+1 开头的顺子个数即可。
- 时间复杂度 O ( N + M ) O(N+M) O(N+M) 。
【代码】
#include
using namespace std; const int MAXN = 1e6 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 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 cnt[MAXN], ans[MAXN]; int dp[MAXN][3][3]; int main() { int n, m; read(n), read(m); for (int i = 1; i <= n; i++) { int x; read(x); cnt[x]++; } memset(dp, -1, sizeof(dp)); dp[1][0][0] = 0; for (int i = 1; i <= m; i++) { for (int j = 0; j <= 2; j++) for (int k = 0; k <= 2; k++) { if (dp[i][j][k] == -1) continue; int tmp = dp[i][j][k]; if (j + k > cnt[i]) continue; for (int l = 0; l <= 2 && j + k + l <= cnt[i]; l++) chkmax(dp[i + 1][k][l], tmp + l + (cnt[i] - j - k - l) / 3); } } writeln(dp[m + 1][0][0]); return 0; }
**【E】**Magic Stones
【思路要点】
- 如果我们将序列 { a 1 , a 2 , a 3 , . . . , a n } \{a_1,a_2,a_3,...,a_n\} {a1,a2,a3,...,an} 描述为 { a 1 , a 2 − a 1 , a 3 − a 2 , . . . , a n − a n − 1 } \{a_1,a_2-a_1,a_3-a_2,...,a_n-a_{n-1}\} {a1,a2−a1,a3−a2,...,an−an−1} ,那么题目中给出的操作相当于交换了该差分数组中相邻的两位。
- 因此,我们只需要比较两个数组的第一位和差分数组即可。
- 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN) 。
【代码】
#include
using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 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 n, a[MAXN], b[MAXN]; int main() { 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 = n; i >= 2; i--) { a[i] -= a[i - 1]; b[i] -= b[i - 1]; } sort(a + 2, a + n + 1); sort(b + 2, b + n + 1); for (int i = 1; i <= n; i++) if (a[i] != b[i]) { puts("No"); return 0; } puts("Yes"); return 0; }
**【F】**Nearest Leaf
【思路要点】
- 离线询问,按照欧拉序遍历整棵树,我们可以用线段树维护出所有节点到当前所在节点的距离,以及该距离的区间最小值,延边移动时进行区间加减即可。
- 时间复杂度 O ( N L o g N + Q L o g N ) O(NLogN+QLogN) O(NLogN+QLogN) 。
【代码】
#include
using namespace std; const int MAXN = 5e5 + 5; const int MAXLOG = 21; const long long INF = 1e18; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 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 SegmentTree { struct Node { int lc, rc; ll tag, Min; } a[MAXN * 2]; int n, size, root; void update(int root) { a[root].Min = min(a[a[root].lc].Min, a[a[root].rc].Min); } void build(int &root, int l, int r, ll *val) { root = ++size; if (l == r) { a[root].Min = val[l]; return; } int mid = (l + r) / 2; build(a[root].lc, l, mid, val); build(a[root].rc, mid + 1, r, val); update(root); } void init(int x, ll *val) { n = x; root = size = 0; build(root, 1, n, val); } void pushdown(int root) { if (a[root].tag == 0) return; a[a[root].lc].tag += a[root].tag; a[a[root].lc].Min += a[root].tag; a[a[root].rc].tag += a[root].tag; a[a[root].rc].Min += a[root].tag; a[root].tag = 0; } void modify(int root, int l, int r, int ql, int qr, int val) { if (l == ql && r == qr) { a[root].tag += val; a[root].Min += val; return; } pushdown(root); int mid = (l + r) / 2; if (mid >= ql) modify(a[root].lc, l, mid, ql, min(qr, mid), val); if (mid + 1 <= qr) modify(a[root].rc, mid + 1, r, max(mid + 1, ql), qr, val); update(root); } void modify(int l, int r, int val) { if (l > r) return; else modify(root, 1, n, l, r, val); } ll query(int root, int l, int r, int ql, int qr) { if (l == ql && r == qr) return a[root].Min; ll ans = INF; pushdown(root); int mid = (l + r) / 2; if (mid >= ql) chkmin(ans, query(a[root].lc, l, mid, ql, min(mid, qr))); if (mid + 1 <= qr) chkmin(ans, query(a[root].rc, mid + 1, r, max(mid + 1, ql), qr)); return ans; } ll query(int l, int r) { if (l > r) return 0; else return query(root, 1, n, l, r); } } ST; int n, q, Max[MAXN]; ll sum[MAXN], ans[MAXN]; bool leaf[MAXN]; vector <pair <int, int> > a[MAXN]; vector <pair <pair <int, int>, int>> b[MAXN]; void dfs(int pos, int fa) { Max[pos] = pos; for (auto x : a[pos]) { sum[x.first] = sum[pos] + x.second; dfs(x.first, x.second); chkmax(Max[pos], Max[x.first]); } } void modify(int l, int r, int val) { ST.modify(l, r, -val); ST.modify(1, l - 1, val); ST.modify(r + 1, n, val); } void work(int pos, int fa) { modify(pos, Max[pos], fa); for (auto x : b[pos]) ans[x.second] = ST.query(x.first.first, x.first.second); for (auto x : a[pos]) work(x.first, x.second); modify(pos, Max[pos], -fa); } int main() { read(n), read(q); for (int i = 1; i <= n; i++) leaf[i] = true; for (int i = 2; i <= n; i++) { int x, y; read(x), read(y); a[x].emplace_back(i, y); leaf[x] = false; } dfs(1, 0); for (int i = 1; i <= n; i++) if (!leaf[i]) sum[i] = INF; for (int i = 1; i <= q; i++) { int pos, l, r; read(pos), read(l), read(r); b[pos].emplace_back(make_pair(l, r), i); } ST.init(n, sum); work(1, 0); for (int i = 1; i <= q; i++) writeln(ans[i]); return 0; }
**【G】**Tree-Tac-Toe
【思路要点】
- 显然,黑棋不会获胜。
- 特殊处理 N ≤ 4 N≤4 N≤4 的情况。
- 当 N ≥ 5 N≥5 N≥5 ,若出现度数 ≥ 4 ≥4 ≥4 的节点,则白胜。
- 若出现度数 = 3 =3 =3 ,且其邻点中有至少 2 2 2 个不是叶子结点的节点,则白胜。
- 若不存在上述两种情况,那么树的形态将会十分单一,即除去叶子节点后只有可能是一条长度至少为 2 2 2 的链,且只有链的两端挂有 1 1 1 或 2 2 2 个叶子结点。
- 在这棵树上,若一个非叶节点已经存在一枚白子,则白胜。
- 若挂有 2 2 2 个叶子结点的一端的某个叶子结点已经存在一枚白子,则白胜。
- 剩余的情况即仅有挂有 1 1 1 个叶子结点的一端可能存在白子,或不存在白子,分类讨论链长的奇偶性即可,具体可见代码。
- 时间复杂度 O ( N ) O(N) O(N) 。
【代码】
#include
using namespace std; const int MAXN = 5e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 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 n; char s[MAXN]; vector <int> a[MAXN]; void solve() { if (n <= 2) { puts("Draw"); return; } if (n == 3) { int cnt = 0; for (int i = 1; i <= n; i++) cnt += s[i] == 'W'; if (cnt >= 2) puts("White"); else puts("Draw"); return; } if (n == 4) { int cnt = 0; bool flg = false; for (int i = 1; i <= n; i++) { cnt += s[i] == 'W'; if (a[i].size() == 3) flg = true; } if (flg) { if (cnt >= 1) puts("White"); else puts("Draw"); } else { for (int i = 1; i <= n; i++) if (s[i] == 'W' && a[i].size() >= 2) { puts("White"); return; } puts("Draw"); } return; } int cnt = 0, tot = 0, spe = 0; for (int i = 1; i <= n; i++) { if (s[i] == 'W') { tot++; if (a[i].size() >= 2 || a[a[i][0]].size() >= 3) { puts("White"); return; } } if (a[i].size() >= 2) cnt++; if (a[i].size() >= 4) { puts("White"); return; } if (a[i].size() >= 3) { int cmt = 0; spe++; for (auto x : a[i]) if (a[x].size() >= 2) cmt++; if (cmt >= 2) { puts("White"); return; } } } assert(cnt >= 2 && tot <= 2); if (tot && spe && cnt % 2 == 1) { puts("White"); return; } if (tot >= 2 && cnt % 2 == 1) { puts("White"); return; } if (spe >= 2 && cnt % 2 == 1) { puts("White"); return; } puts("Draw"); } int main() { int T; read(T); while (T--) { read(n); for (int i = 1; i <= n; i++) a[i].clear(); for (int i = 1; i <= n - 1; i++) { int x, y; read(x), read(y); a[x].push_back(y); a[y].push_back(x); } scanf("\n%s", s + 1); solve(); } return 0; }
**【H】**Modest Substrings
【思路要点】
- 我们可以将 l l l 至 r r r 之间的所有整数描述为大约 ( ∣ l ∣ + ∣ r ∣ ) ∗ 10 (|l|+|r|)*10 (∣l∣+∣r∣)∗10 个带有通配符后缀的字符串的并集,例如 l = 10 , r = 34 l=10,r=34 l=10,r=34 时,该集合即为 { 1 ∗ , 2 ∗ , 30 , 31 , 32 , 33 , 34 } \{1*,2*,30,31,32,33,34\} {1∗,2∗,30,31,32,33,34} 。注意任意一个字符串只会匹配集合中至多一个元素。
- 将所有集合内的字符串除去通配符后缀后建立 A C AC AC 自动机,再在每一个节点上记录该节点具有的通配符后缀的长度集合。由于字符串集合的特殊性,该 A C AC AC 自动机的点数依然是 O ( ( ∣ l ∣ + ∣ r ∣ ) ∗ 10 ) O((|l|+|r|)*10) O((∣l∣+∣r∣)∗10) 级别的。
- 考虑用动态规划解题,记 d p i , j dp_{i,j} dpi,j 表示当前字符串长度为 i i i ,在 A C AC AC 自动机上匹配到的节点为 j j j 的最优答案的值。转移时枚举出边 k k k ,记到达的点为 d e s t dest dest ,则将 d p i , j dp_{i,j} dpi,j 加上 d e s t dest dest 的 f a i l fail fail 链上所有节点长度在 N − i − 1 N-i-1 N−i−1 以内的通配符后缀的个数转移到 d p i + 1 , d e s t dp_{i+1,dest} dpi+1,dest 。
- 时间复杂度 O ( ( ∣ l ∣ + ∣ r ∣ ) ∗ 1 0 2 ∗ N ) O((|l|+|r|)*10^2*N) O((∣l∣+∣r∣)∗102∗N) 。
【代码】
#include
using namespace std; const int MAXN = 20005; const int MAXM = 2005; const int MAXL = 805; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 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 ACAutomaton { struct Node { int child[10]; int fail, sum[MAXL]; } a[MAXN]; int lenl, lenr, root, size, dp[MAXM][MAXN]; char l[MAXN], r[MAXN]; void buildfail() { int l = 0, r = -1; static int q[MAXN]; a[root].fail = root; for (int i = 0; i <= 9; i++) if (a[root].child[i]) { a[a[root].child[i]].fail = root; q[++r] = a[root].child[i]; } else a[root].child[i] = root; while (l <= r) { int pos = q[l++]; for (int i = 0; i <= lenr; i++) a[pos].sum[i] += a[a[pos].fail].sum[i]; for (int i = 0; i <= 9; i++) if (a[pos].child[i]) { a[a[pos].child[i]].fail = a[a[pos].fail].child[i]; q[++r] = a[pos].child[i]; } else a[pos].child[i] = a[a[pos].fail].child[i]; } for (int i = 0; i <= size; i++) for (int j = 1; j <= lenr; j++) a[i].sum[j] += a[i].sum[j - 1]; } void init() { scanf("%s", l + 1); scanf("%s", r + 1); lenl = strlen(l + 1); lenr = strlen(r + 1); root = size = 0; if (lenl == lenr) { bool flg = false; for (int i = 1, posl = root, posr = root; i <= lenl; posl = a[posl].child[l[i] - '0'], posr = a[posr].child[r[i] - '0'], i++) { if (flg) { for (int j = l[i] - '0'; j <= 9; j++) { if (a[posl].child[j] == 0) a[posl].child[j] = ++size; if (i == lenl || j != l[i] - '0') a[a[posl].child[j]].sum[lenl - i] = 1; } for (int j = 0; j <= r[i] - '0'; j++) { if (a[posr].child[j] == 0) a[posr].child[j] = ++size; if (i == lenl || j != r[i] - '0') a[a[posr].child[j]].sum[lenl - i] = 1; } } else { for (int j = l[i] - '0'; j <= r[i] - '0'; j++) { if (a[posl].child[j] == 0) a[posl].child[j] = ++size; if (i == lenl || j != l[i] - '0' && j != r[i] - '0') a[a[posl].child[j]].sum[lenl - i] = 1; } } flg |= l[i] != r[i]; } } else { for (int i = 1, pos = root; i <= lenl; pos = a[pos].child[l[i] - '0'], i++) { for (int j = l[i] - '0'; j <= 9; j++) { if (a[pos].child[j] == 0) a[pos].child[j] = ++size; if (i == lenl || j != l[i] - '0') a[a[pos].child[j]].sum[lenl - i] = 1; } } for (int i = 1, pos = root; i <= lenr; pos = a[pos].child[r[i] - '0'], i++) { for (int j = 0 + (i == 1); j <= r[i] - '0'; j++) { if (a[pos].child[j] == 0) a[pos].child[j] = ++size; if (i == lenr || j != r[i] - '0') a[a[pos].child[j]].sum[lenr - i] = 1; } } for (int i = lenl + 1; i <= lenr - 1; i++) { for (int j = 1; j <= 9; j++) { if (a[root].child[j] == 0) a[root].child[j] = ++size; a[a[root].child[j]].sum[i - 1] = 1; } } } buildfail(); } void query(int n) { memset(dp, -1, sizeof(dp)), dp[0][0] = 0; for (int i = 0; i <= n - 1; i++) for (int j = 0; j <= size; j++) { if (dp[i][j] == -1) continue; int tmp = dp[i][j]; for (int k = 0; k <= 9; k++) { int dest = a[j].child[k]; chkmax(dp[i + 1][dest], tmp + a[dest].sum[min(n - i - 1, lenr)]); } } int ans = 0; for (int i = 0; i <= size; i++) chkmax(ans, dp[n][i]); printf("%d\n", ans); bool opt[MAXM][MAXN]; for (int i = 0; i <= size; i++) if (dp[n][i] == ans) opt[n][i] = true; for (int i = n - 1; i >= 0; i--) for (int j = 0; j <= size; j++) { if (dp[i][j] == -1) continue; int tmp = dp[i][j]; for (int k = 0; k <= 9; k++) { int dest = a[j].child[k]; if (opt[i + 1][dest] && dp[i + 1][dest] == tmp + a[dest].sum[min(n - i - 1, lenr)]) opt[i][j] = true; } } assert(opt[0][0]); int pos = 0; for (int i = 1; i <= n; i++) { int tmp = dp[i - 1][pos]; for (int j = 0; j <= 9; j++) { int dest = a[pos].child[j]; if (opt[i][dest] && dp[i][dest] == tmp + a[dest].sum[min(n - i, lenr)]) { printf("%d", j); pos = a[pos].child[j]; break; } } } printf("\n"); } } ACAM; int main() { ACAM.init(); int n; read(n); ACAM.query(n); return 0; }