2022 暑假水题选做
20220627
P3914 染色计数
思路:考虑树形 dp。设
后面的
算法:树形 dp。
技巧:通过处理某些东西优化复杂度。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 5000, P = 1e9 + 7; struct Edge { int to, nxt; } e[N * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v) { e[++tote] = {v, head[u]}; head[u] = tote; } int n, m, f[N + 10][N + 10], sum[N + 10]; void dfs(int u, int _fa) { for (int _ = head[u]; _; _ = e[_].nxt) { int v = e[_].to; if (v == _fa) continue; dfs(v, u); for (int i = 1; i <= m; i++) f[u][i] = 1LL * f[u][i] * ((sum[v] - f[v][i]) % P) % P; } for (int i = 1; i <= m; i++) (sum[u] += f[u][i]) %= P; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { int x; scanf("%d", &x); while (x--) { int y; scanf("%d", &y); f[i][y] = 1; } } for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); addEdge(u, v), addEdge(v, u); } dfs(1, 0); printf("%d\n", (sum[1] % P + P) % P); return 0; }
CF1677C Tokitsukaze and Two Colorful Tapes
思路:先拆置换,对于每个置换,填法一定是
算法:贪心。
技巧:拆置换、分别考虑每个数对答案的贡献。
想到了的:拆置换、贪心。
没想到的:考虑贡献。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 1e5; int n, a[N + 10], b[N + 10], pos[N + 10]; bool vis[N + 10]; void mian() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); for (int i = 1; i <= n; i++) scanf("%d", b + i), pos[b[i]] = i; int peak = 0; for (int i = 1; i <= n; i++) { int x = i, cnt = 0; while (1) { if (vis[x]) break; vis[x] = 1; cnt++; x = pos[a[x]]; } peak += cnt / 2; } printf("%lld\n", 2LL * peak * (n - peak)); } int main() { int T; scanf("%d", &T); while (T--) { for (int i = 1; i <= n; i++) a[i] = b[i] = pos[i] = vis[i] = 0; mian(); } return 0; }
CF1665D GCD Guess
思路:考虑分别算出每个二进制位上的数字。假设我们知道了第
算法:gcd、位运算。
技巧:按位考虑、构造。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> using namespace std; int query(int a, int b) { printf("? %d %d\n", a, b); fflush(stdout); int x; scanf("%d", &x); return x; } void submit(int x) { printf("! %d\n", x); fflush(stdout); } void mian() { int ans = 0; for (int i = 0; i < 30; i++) { if (query((1 << i) - ans, (1 << (i + 1)) + (1 << i) - ans) == (1 << (i + 1))) ans |= (1 << i); } submit(ans); } int main() { int T; scanf("%d", &T); while (T--) mian(); return 0; }
20220628
CF1696D Permutation Graph
思路:考虑贪心:每次都尽可能往右走。在当前位于
算法:贪心。
技巧:单调栈预处理。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <stack> using namespace std; #define eprintf(...) fprintf(stderr, __VA_ARGS__) const int N = 2.5e5; int n, a[N + 10], pre[N + 10], suc[N + 10]; void mian() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); stack<int> stk; stk.push(n + 1); for (int i = n; i >= 1; i--) { while (stk.size() > 1 && a[stk.top()] < a[i]) stk.pop(); suc[i] = stk.top(); stk.push(i); } while (!stk.empty()) stk.pop(); stk.push(n + 1); for (int i = n; i >= 1; i--) { while (stk.size() > 1 && a[stk.top()] > a[i]) stk.pop(); pre[i] = stk.top(); stk.push(i); } for (int i = 1; i <= n; i++) eprintf("de: %d %d\n", pre[i], suc[i]); eprintf("\n"); int now = 1, ans = 0; while (now < n) { if (a[now] < a[now + 1]) { int lim = pre[now] - 1; while (suc[now] <= lim) now = suc[now]; } else { int lim = suc[now] - 1; while (pre[now] <= lim) now = pre[now]; } ans++; } printf("%d\n", ans); } int main() { int T; scanf("%d", &T); while (T--) { for (int i = 1; i <= n; i++) a[i] = pre[i] = suc[i] = 0; mian(); } return 0; }
CF1695C Zero Path
思路:设
可以用 std::bitset
优化。
算法:dp。
技巧:压位优化 dp。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <bitset> using namespace std; const int N = 1e3; int n, m, a[N + 10][N + 10]; bitset<2001> f[N + 10][N + 10]; // -1000 ~ 1000 void mian() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", a[i] + j); f[0][1][1001] = f[1][0][1001] = 1; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) { if (a[i][j] == 1) f[i][j] = (f[i - 1][j] << 1) | (f[i][j - 1] << 1); else f[i][j] = (f[i - 1][j] >> 1) | (f[i][j - 1] >> 1); } puts(f[n][m][1001] == 1 ? "YES" : "NO"); } int main() { int T; scanf("%d", &T); while (T--) { for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) a[i][j] = 0, f[i][j].reset(); mian(); } return 0; }
CF1696E Placing Jinas
思路:打表找规律,发现把
算法:组合数。
技巧:找规律、推式子。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 2e5, P = 1e9 + 7; int n, a[N + 10], fac[N * 2 + 10], ifac[N * 2 + 10]; int qpow(int a, int b) { int res = 1; while (b) { if (b & 1) res = 1LL * a * res % P; a = 1LL * a * a % P; b >>= 1; } return res; } void init() { fac[0] = 1; for (int i = 1; i <= N * 2; i++) fac[i] = 1LL * fac[i - 1] * i % P; ifac[N * 2] = qpow(fac[N * 2], P - 2); for (int i = 2 * N - 1; i >= 0; i--) ifac[i] = 1LL * ifac[i + 1] * (i + 1) % P; } int C(int a, int b) { if (a < b) return 0; return 1LL * fac[a] * ifac[b] % P * ifac[a - b] % P; } int main() { scanf("%d", &n); for (int i = 0; i <= n; i++) scanf("%d", a + i); init(); int ans = 0; for (int i = 0; i <= n; i++) (ans += C(i + a[i], a[i] - 1)) %= P; printf("%d\n", ans); return 0; }
CF1696C Fishingprince Plays With Array
思路:观察到两个操作互为逆向操作,考虑找到一个中间状态。对于本题,中间状态可以选择拆分到不能拆时的数列。
算法:?
技巧:考虑找中间状态。
想到了的:两个操作互为逆向操作。
没想到的:考虑找中间状态。
代码
#include <algorithm> #include <cstdio> #include <functional> #include <vector> using namespace std; typedef long long ll; int main() { int T; scanf("%d", &T); while (T--) { int n, m, k; scanf("%d%d", &n, &m); vector<int> a(n); for (auto &x : a) scanf("%d", &x); scanf("%d", &k); vector<int> b(k); for (auto &x : b) scanf("%d", &x); auto split = [&](vector<int> vec) { vector<pair<int, ll>> res; for (auto x : vec) { int cnt = 1; while (x % m == 0) cnt *= m, x /= m; if (!res.empty() && res.back().first == x) res.back().second += cnt; else res.push_back({x, cnt}); } return res; }; if (split(a) == split(b)) puts("Yes"); else puts("No"); } return 0; }
20220630
CF1698E PermutationForces II
思路:考虑到第
算法:计数。
技巧:观察性质、猜结论(?)。
想到了的:无。
没想到的:都没想到。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 2e5, P = 998244353; int n, s; pair<int, int> a[N + 10]; bool vis[N + 10]; int b[N + 10], c[N + 10]; void mian() { scanf("%d%d", &n, &s); for (int i = 1; i <= n; i++) scanf("%d", &a[i].first); for (int i = 1; i <= n; i++) { scanf("%d", &a[i].second); if (a[i].second != -1) vis[a[i].second] = 1; } int m = 0; for (int i = 1; i <= n; i++) if (!vis[i]) b[++m] = i; int mx = 0; for (int i = 1; i <= n; i++) { if (a[i].second != -1) mx = max(mx, a[i].first - a[i].second); } if (mx > s) return puts("0"), void(); int totc = 0; for (int i = 1; i <= n; i++) { if (a[i].second == -1) c[++totc] = b + m + 1 - lower_bound(b + 1, b + m + 1, a[i].first - s); } sort(c + 1, c + m + 1); int ans = 1; for (int i = 1; i <= m; i++) ans = 1LL * ans * (c[i] - (i - 1)) % P; printf("%d\n", ans); } int main() { int T; scanf("%d", &T); while (T--) { for (int i = 1; i <= n; i++) vis[i] = 0; mian(); } return 0; }
20220705
CF938D Buy a Ticket
思路:考虑建一个新点
算法:最短路。
技巧:建新点、把路径倒过来考虑。
想到了的:(课上例题)
没想到的:(课上例题)
代码
#include <algorithm> #include <cstdio> #include <cstring> #include <queue> using namespace std; typedef long long ll; const int N = 2e5, M = 5e5; struct Edge { int to, nxt; ll w; } e[M * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v, ll w) { e[++tote] = {v, head[u], w}; head[u] = tote; } int n, m; ll dis[N + 10]; bool vis[N + 10]; void dijkstra() { memset(dis, 0x3f, sizeof(dis)); dis[0] = 0; priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> q; q.push({dis[0], 0}); while (!q.empty()) { auto _ = q.top(); q.pop(); int u = _.second; if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] > dis[u] + e[i].w) { dis[v] = dis[u] + e[i].w; q.push({dis[v], v}); } } } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) { int u, v; ll w; scanf("%d%d%lld", &u, &v, &w); w *= 2; addEdge(u, v, w), addEdge(v, u, w); } for (int i = 1; i <= n; i++) { ll x; scanf("%lld", &x); addEdge(0, i, x), addEdge(i, 0, x); } dijkstra(); for (int i = 1; i <= n; i++) printf("%lld%c", dis[i], " \n"[i == n]); return 0; }
20220709
CF1701C Schedule Management
思路:二分答案
算法:二分答案、贪心。
技巧:观察到能搞出来的时间是连续的。
想到了的:无。
没想到的:都没想到。
代码
const int N = 2e5; int n, m, a[N + 10], cnt[N + 10]; bool check(int x) { ll y = 0; for (int i = 1; i <= n; i++) { if (x >= cnt[i]) y += 0LL + cnt[i] + (x - cnt[i]) / 2; else y += 0LL + x; } return y >= m; } void mian() { for (int i = 1; i <= n; i++) cnt[i] = 0; scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) scanf("%d", a + i), cnt[a[i]]++; int l = 0, r = 2 * m + 1; while (l < r) { int mid = (l + r) >> 1; if (check(mid)) r = mid; else l = mid + 1; } printf("%d\n", l); }
CF1701D Permutation Restoration
思路:题目中的条件是 std::set
维护还没有配上对的 std::set
里比
算法:贪心。
技巧:std::set
、关于线段的贪心。
想到了的:卡范围、排序、贪心。
没想到的:数据结构维护(std::set
)。
代码
const int N = 5e5; struct Node { int l, r, id; }; int n, a[N + 10], b[N + 10]; Node seg[N + 10]; void mian() { for (int i = 1; i <= n; i++) a[i] = b[i] = 0, seg[i].l = seg[i].r = seg[i].id = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", b + i); for (int i = 1; i <= n; i++) if (b[i] == 0) seg[i] = {i + 1, n, i}; else seg[i] = {i / (b[i] + 1) + 1, i / b[i], i}; sort(seg + 1, seg + n + 1, [](const Node &x, const Node &y) { return x.r == y.r ? x.l < y.l : x.r < y.r; }); set<int> s; for (int i = 1; i <= n; i++) s.insert(i); for (int i = 1; i <= n; i++) { auto it = s.lower_bound(seg[i].l); a[seg[i].id] = *it; s.erase(it); } for (int i = 1; i <= n; i++) printf("%d%c", a[i], " \n"[i == n]); }
20220717
P2899 [USACO08JAN]Cell Phone Network G
思路:设
但是对于
算法:树形 dp。
技巧:无。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 10000; struct Edge { int to, nxt; } e[N * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v) { e[++tote] = {v, head[u]}; head[u] = tote; } // f[u][0] -> self // f[u][1] -> parent // f[u][2] -> child int n, f[N + 10][3]; void dfs(int u, int _fa) { bool flg = 0; int add = 0x3f3f3f3f; f[u][0] = 1; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v == _fa) continue; dfs(v, u); f[u][0] += min({f[v][0], f[v][1], f[v][2]}); f[u][1] += min(f[v][0], f[v][2]); if (f[v][0] <= f[v][2]) flg = 1, f[u][2] += f[v][0]; else add = min(add, f[v][0] - f[v][2]), f[u][2] += f[v][2]; } if (!flg) f[u][2] += add; } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); addEdge(u, v); addEdge(v, u); } dfs(1, 0); printf("%d\n", min(f[1][0], f[1][2])); return 0; }
CF1699C The Third Problem
思路:考虑从限制最多的
算法:计数。
技巧:从限制多的开始考虑。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 1e5, P = int(1e9) + 7; int n, a[N + 10], pos[N + 10]; void mian() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i), pos[a[i]] = i; int l = pos[0], r = pos[0], ans = 1; for (int i = 1; i < n; i++) { if (pos[i] < l) l = pos[i]; else if (pos[i] > r) r = pos[i]; else ans = 1LL * ans * (r - l + 1 - i) % P; } printf("%d\n", ans); } int main() { int T; scanf("%d", &T); while (T--) { for (int i = 0; i <= n; i++) a[i] = pos[i] = 0; mian(); } return 0; }
20220718
CF1093D Beautiful Graph
思路:如果图中的某个连通块不是二分图则无解。否则每个连通块的答案就是这个二分图两侧结点数的
算法:二分图。
技巧:图上奇偶性问题考虑二分图。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <queue> using namespace std; const int N = 3e5, P = 998244353; struct Edge { int to, nxt; } e[N * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v) { e[++tote] = {v, head[u]}; head[u] = tote; } int n, m, col[N + 10]; int siz1, siz2, vis[N + 10]; int pw[N + 10]; bool isBi(int s) { queue<int> q; q.push(s); col[s] = 1; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (col[v] == 0) { col[v] = 3 - col[u]; q.push(v); } else if (col[v] == col[u]) return 0; } } return 1; } void dfs(int u) { if (vis[u]) return; vis[u] = 1; if (col[u] == 1) siz1++; else siz2++; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; dfs(v); } } void mian() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) { int u, v; scanf("%d%d", &u, &v); addEdge(u, v), addEdge(v, u); } int ans = 1; for (int i = 1; i <= n; i++) if (!vis[i]) { siz1 = siz2 = 0; if (!isBi(i)) return puts("0"), void(); dfs(i); ans = 1LL * ans * ((pw[siz1] + pw[siz2]) % P) % P; } printf("%d\n", ans); } int main() { pw[0] = 1; for (int i = 1; i <= N; i++) pw[i] = (pw[i - 1] + pw[i - 1]) % P; int T; scanf("%d", &T); while (T--) { tote = 0; for (int i = 1; i <= n; i++) vis[i] = col[i] = head[i] = 0; mian(); } return 0; }
CF1081D Maximum Distance
思路:由 Kruskal 算法的性质,答案所在的边一定在 MST 上。并且把这条边从 MST 上割掉后两个连通块都有特殊点。最后输出
算法:MST。
技巧:Kruskal 算法的性质。
想到了的:无。
没想到的:都没想到。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 1e5; struct Edge { int u, v, w; }; Edge e[N + 10]; int n, m, k, spec[N + 10]; int f[N + 10]; int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } int main() { scanf("%d%d%d", &n, &m, &k); for (int i = 1; i <= k; i++) { int x; scanf("%d", &x); spec[x] = 1; } for (int i = 1; i <= m; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w); sort(e + 1, e + m + 1, [](const Edge &lhs, const Edge &rhs) { return lhs.w < rhs.w; }); int ans = 0; for (int i = 1; i <= n; i++) f[i] = i; for (int i = 1; i <= m; i++) { int u = e[i].u, v = e[i].v; u = find(u), v = find(v); if (u != v) { f[u] = v ; if (spec[u] && spec[v]) ans = e[i].w; if (spec[u] || spec[v]) spec[u] = spec[v] = 1; } } for (int i = 1; i <= k; i++) printf("%d%c", ans, " \n"[i == k]); return 0; }
CF1585F Non-equal Neighbours
思路:首先有一个暴力的 dp:设
这样是
算法:数据结构优化 dp。
技巧:数据结构优化。
想到了的:朴素 dp,压维。
没想到的:线段树优化。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 2e5, P = 998244353; struct Node { int sum, atag, mtag; Node() : sum(0), atag(0), mtag(1) {} }; Node t[N * 4 + 10]; int n, a[N + 10], m, b[N + 10]; #define ls(x) (x << 1) #define rs(x) (x << 1 | 1) void pushUp(int i) { t[i].sum = (t[ls(i)].sum + t[rs(i)].sum) % P; } void pushA(int i, int l, int r, int atag) { (t[i].atag += atag) %= P; (t[i].sum += 1LL * atag * (b[r] - b[l - 1]) % P) %= P; } void pushM(int i, int mtag) { t[i].mtag = 1LL * t[i].mtag * mtag % P; t[i].atag = 1LL * t[i].atag * mtag % P; t[i].sum = 1LL * t[i].sum * mtag % P; } void pushDown(int i, int l, int r) { if (t[i].atag != 0 || t[i].mtag != 1) { int mid = (l + r) >> 1; pushM(ls(i), t[i].mtag); pushM(rs(i), t[i].mtag); pushA(ls(i), l, mid, t[i].atag); pushA(rs(i), mid + 1, r, t[i].atag); t[i].atag = 0, t[i].mtag = 1; } } void build(int i, int l, int r) { if (l == r) return t[i].sum = b[l] - b[l - 1], void(); int mid = (l + r) >> 1; build(ls(i), l, mid); build(rs(i), mid + 1, r); pushUp(i); } void modifyAdd(int i, int l, int r, int ql, int qr, int v) { if (qr < l || r < ql) return; if (ql <= l && r <= qr) return pushA(i, l, r, v), void(); int mid = (l + r) >> 1; pushDown(i, l, r); modifyAdd(ls(i), l, mid, ql, qr, v); modifyAdd(rs(i), mid + 1, r, ql, qr, v); pushUp(i); } void modifyMul(int i, int l, int r, int ql, int qr, int v) { if (qr < l || r < ql) return; if (ql <= l && r <= qr) return pushM(i, v), void(); int mid = (l + r) >> 1; pushDown(i, l, r); modifyMul(ls(i), l, mid, ql, qr, v); modifyMul(rs(i), mid + 1, r, ql, qr, v); pushUp(i); } #undef ls #undef rs int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i), b[i] = a[i]; sort(b + 1, b + n + 1); m = int(unique(b + 1, b + n + 1) - b - 1); for (int i = 1; i <= n; i++) a[i] = int(lower_bound(b + 1, b + m + 1, a[i]) - b); build(1, 1, m); modifyMul(1, 1, m, a[1] + 1, m, 0); for (int i = 2; i <= n; i++) { int sum = t[1].sum; modifyMul(1, 1, m, 1, m, -1); modifyAdd(1, 1, m, 1, m, sum); modifyMul(1, 1, m, a[i] + 1, m, 0); } printf("%d\n", (t[1].sum % P + P) % P); return 0; }
20220719
CF1706E Qpwoeirut and Vertices
思路:由性质得答案一定在 MST 上。而树上
算法:MST,线段树。
技巧:Kruskal 算法性质,树上路径并相关结论。
想到了的:考虑 MST。
没想到的:那个结论。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 1e5, M = 2e5, L = 20; struct Edge { int to, nxt, w; } e[N * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v, int w) { e[++tote] = {v, head[u], w}; head[u] = tote; } struct Edge2 { int u, v, w; Edge2() : u(0), v(0), w(0) {} }; struct Node { int mx, rval; Node() : mx(0), rval(0) {} }; int n, m, q; Edge2 E[M + 10]; int f[N + 10]; int fa[N + 10][L + 5], dep[N + 10], mx[N + 10][L + 5], val[N + 10]; Node t[N * 4 + 10]; int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } void kruskal() { for (int i = 1; i <= n; i++) f[i] = i; sort(E + 1, E + m + 1, [](const Edge2 &lhs, const Edge2 &rhs) { return lhs.w < rhs.w; }); for (int i = 1; i <= m; i++) { int u = E[i].u, v = E[i].v; u = find(u), v = find(v); if (u != v) { f[u] = v; addEdge(E[i].u, E[i].v, E[i].w); addEdge(E[i].v, E[i].u, E[i].w); } } } void dfs(int u, int ffa) { fa[u][0] = ffa; for (int i = 1; i <= L; i++) { fa[u][i] = fa[fa[u][i - 1]][i - 1]; mx[u][i] = max(mx[u][i - 1], mx[fa[u][i - 1]][i - 1]); } dep[u] = dep[ffa] + 1; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v == ffa) continue; mx[v][0] = e[i].w; dfs(v, u); } } int lca(int u, int v) { if (dep[u] < dep[v]) swap(u, v); int res = 0; for (int i = L; i >= 0; i--) if (dep[fa[u][i]] >= dep[v]) { res = max(res, mx[u][i]); u = fa[u][i]; } if (u == v) return res; for (int i = L; i >= 0; i--) if (fa[u][i] != fa[v][i]) { res = max({res, mx[u][i], mx[v][i]}); u = fa[u][i], v = fa[v][i]; } return max({res, mx[u][0], mx[v][0]}); } #define ls(x) (x << 1) #define rs(x) (x << 1 | 1) Node pushUp(const Node &L, const Node &R) { Node res; res.mx = max({L.mx, R.mx, L.rval}); res.rval = R.rval; return res; } void build(int i, int l, int r) { if (l == r) { t[i].rval = val[l]; t[i].mx = 0; return; } int mid = (l + r) >> 1; build(ls(i), l, mid); build(rs(i), mid + 1, r); t[i] = pushUp(t[ls(i)], t[rs(i)]); } Node query(int i, int l, int r, int ql, int qr) { if (qr < l || r < ql) return Node(); if (ql <= l && r <= qr) return t[i]; int mid = (l + r) >> 1; Node L = query(ls(i), l, mid, ql, qr); Node R = query(rs(i), mid + 1, r, ql, qr); if (qr <= mid) return L; if (ql > mid) return R; return pushUp(L, R); } #undef ls #undef rs void mian() { scanf("%d%d%d", &n, &m, &q); for (int i = 1; i <= m; i++) scanf("%d%d", &E[i].u, &E[i].v), E[i].w = i; kruskal(); dfs(1, 0); for (int i = 1; i < n; i++) val[i] = lca(i, i + 1); build(1, 1, n); while (q--) { int l, r; scanf("%d%d", &l, &r); printf("%d ", query(1, 1, n, l, r).mx); } puts(""); } int main() { int T; scanf("%d", &T); while (T--) { tote = 0; for (int i = 1; i <= n; i++) { head[i] = dep[i] = val[i] = f[i] = 0; for (int j = 0; j <= L; j++) fa[i][j] = mx[i][j] = 0; } for (int i = 1; i <= m; i++) E[i] = Edge2(); for (int i = 1; i <= n * 4; i++) t[i] = Node(); mian(); } return 0; }
20220721
CF920E Connected Components?
思路:直接暴力,复杂度是对的。
算法:暴力。
技巧:暴力。
想到了的:无。
没想到的:都没想到。
代码
#include <algorithm> #include <cstdio> #include <queue> #include <set> using namespace std; const int N = 2e5; int n, m; set<int> G[N + 10], st; multiset<int> ans; bool vis[N + 10]; int siz; void bfs(int u) { queue<int> q; vis[u] = 1, st.erase(u), q.push(u); while(!q.empty()) { int u = q.front(); q.pop(); siz++; set<int> _ = st; for (auto v : _) if (G[u].find(v) == G[u].end() && !vis[v]) vis[v] = 1, st.erase(v), q.push(v); } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) { int u, v; scanf("%d%d", &u, &v); G[u].insert(v), G[v].insert(u); } for (int i = 1; i <= n; i++) st.insert(i); for (int i = 1; i <= n; i++) if (!vis[i]) { siz = 0; bfs(i); ans.insert(siz); } printf("%d\n", int(ans.size())); for (auto x : ans) printf("%d ", x); puts(""); return 0; }
20220723
P3873 [TJOI2010]天气预报
思路:考虑构造转移矩阵:
算法:矩阵。
技巧:矩阵加速递推。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <cstring> using namespace std; const int N = 100, P = 4147; int n, m; struct Matrix { int a[N + 10][N + 10]; Matrix() { memset(a, 0, sizeof(a)); } int *operator[](const int i) { return a[i]; } const int *operator[](const int i) const { return a[i]; } void unit(int n) { for (int i = 1; i <= n; i++) a[i][i] = 1; } }; Matrix operator*(const Matrix &A, const Matrix &B) { Matrix res; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) for (int k = 1; k <= n; k++) (res[i][j] += 1LL * A[i][k] * B[k][j] % P) %= P; return res; } Matrix qpow(Matrix A, int b) { Matrix res; res.unit(n); while (b) { if (b & 1) res = res * A; A = A * A; b >>= 1; } return res; } int main() { scanf("%d%d", &n, &m); Matrix A, W; for (int i = 1; i <= n; i++) scanf("%d", &W[1][i]); for (int i = 1; i <= n; i++) scanf("%d", &A[i][1]); for (int i = 1; i <= n; i++) A[i - 1][i] = 1; printf("%d\n", (W * qpow(A, m - n))[1][1]); return 0; }
ABC261E Many Operations
思路:按位考虑。于是我们可以预处理
算法:位运算。
技巧:按位考虑。
想到了的:都想到了。
没想到的:无。
代码
const int N = 2e5, M = 30; int n, x, opt[N + 10], a[N + 10]; int f[N + 10][M + 5][2]; void mian() { scanf("%d%d", &n, &x); for (int i = 1; i <= n; i++) scanf("%d%d", opt + i, a + i); for (int j = 0; j <= M; j++) for (int k = 0; k < 2; k++) { f[0][j][k] = k; for (int i = 1; i <= n; i++) { if (opt[i] == 1) f[i][j][k] = (f[i - 1][j][k] & ((a[i] >> j) & 1)); if (opt[i] == 2) f[i][j][k] = (f[i - 1][j][k] | ((a[i] >> j) & 1)); if (opt[i] == 3) f[i][j][k] = (f[i - 1][j][k] ^ ((a[i] >> j) & 1)); } } for (int i = 1; i <= n; i++) { int y = 0; for (int j = 0; j <= M; j++) if (f[i][j][(x >> j) & 1]) y |= (1 << j); printf("%d\n", y); x = y; } }
ABC261F Sorting Color Balls
思路:答案即不考虑颜色时的逆序对数减去相同颜色逆序对数。
- 对于一个序列,每次交换相邻的两个数,使它升序的最小操作次数为这个序列的逆序对数!对于一个序列,每次交换相邻的两个数,使它升序的最小操作次数为这个序列的逆序对数!对于一个序列,每次交换相邻的两个数,使它升序的最小操作次数为这个序列的逆序对数!
- 正难则反!正难则反!正难则反!
算法:逆序对。
技巧:对于一个序列,每次交换相邻的两个数,使它升序的最小操作次数为这个序列的逆序对数;正难则反。
想到了的:都想到了。
没想到的:无。
代码
const int N = 3e5; struct Node { int c, x; }; int n, m; Node a[N + 10]; ll ans; int c[N + 10]; int qwq[N + 10], tmp[N + 10]; vector<int> pos[N + 10]; #define lowbit(x) (x & (-x)) void modify(int p, int x) { for (; p <= m; p += lowbit(p)) c[p] += x; } int query(int p) { int res = 0; for (; p; p -= lowbit(p)) res += c[p]; return res; } #undef lowbit ll calc(int n, int *a) { for (int i = 1; i <= m; i++) c[i] = 0; for (int i = 1; i <= n; i++) tmp[i] = a[i]; sort(tmp + 1, tmp + n + 1); m = int(unique(tmp + 1, tmp + n + 1) - tmp - 1); for (int i = 1; i <= n; i++) a[i] = int(lower_bound(tmp + 1, tmp + m + 1, a[i]) - tmp); ll res = 0; for (int i = 1; i <= n; i++) { res += query(m) - query(a[i]); modify(a[i], 1); } return res; } void mian() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i].c); for (int i = 1; i <= n; i++) scanf("%d", &a[i].x), qwq[i] = a[i].x; ans = calc(n, qwq); for (int i = 1; i <= n; i++) pos[a[i].c].push_back(a[i].x); for (int i = 1; i <= n; i++) { int tot = 0; for (auto x : pos[i]) qwq[++tot] = x; if (tot == 0) continue; ans -= calc(int(pos[i].size()), qwq); } printf("%lld\n", ans); }
20220724
P2458 [SDOI2006]保安站岗
同这个题。
P7689 [CEOI2002] Bugs Integrated,Inc.
见题解。
20220725
CF1527E Partition Game
思路:设
算法:决策单调性分治优化 dp。
技巧:决策单调性分治。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <cstring> using namespace std; typedef long long ll; const int N = 35000, K = 100; int n, k, a[N + 10], pre[N + 10], suc[N + 10], pos[N + 10]; ll f[K + 5][N + 10], res; int nowl, nowr; ll calc(int l, int r) { while (nowl > l) nowl--, res += suc[nowl] <= nowr ? suc[nowl] - nowl : 0; while (nowr < r) nowr++, res += pre[nowr] >= nowl ? nowr - pre[nowr] : 0; while (nowl < l) res -= suc[nowl] <= nowr ? suc[nowl] - nowl : 0, nowl++; while (nowr > r) res -= pre[nowr] >= nowl ? nowr - pre[nowr] : 0, nowr--; return res; } void solve(int j, int l, int r, int L, int R) { if (l > r || L > R) return; int mid = (l + r) >> 1, p = 0; for (int i = L; i <= min(R, mid); i++) { ll val = f[j - 1][i - 1] + calc(i, mid); if (val <= f[j][mid]) f[j][mid] = val, p = i; } solve(j, l, mid - 1, L, p); solve(j, mid + 1, r, p, R); } int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", a + i); for (int i = 1; i <= n; i++) pre[i] = pos[a[i]], pos[a[i]] = i; for (int i = 1; i <= n; i++) pos[a[i]] = n + 1; for (int i = n; i >= 1; i--) suc[i] = pos[a[i]], pos[a[i]] = i; memset(f, 0x3f, sizeof(f)); f[0][0] = 0; nowl = 1, nowr = 0; for (int i = 1; i <= k; i++) solve(i, 1, n, 1, n); printf("%lld\n", f[k][n]); return 0; }
CF868F Yet Another Minimization Problem
思路:设
算法:决策单调性分治优化 dp。
技巧:决策单调性分治。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <cstring> using namespace std; typedef long long ll; const int N = 2e5, K = 20; int n, k, a[N + 10]; ll f[K + 5][N + 10], res; int buc[N + 10], nowl, nowr; void add(int x) { res += buc[a[x]]; buc[a[x]]++; } void del(int x) { buc[a[x]]--; res -= buc[a[x]]; } ll calc(int l, int r) { while (nowl > l) add(--nowl); while (nowr < r) add(++nowr); while (nowl < l) del(nowl++); while (nowr > r) del(nowr--); return res; } void solve(int j, int l, int r, int L, int R) { if (l > r || L > R) return; int mid = (l + r) >> 1, pos = 0; for (int i = L; i <= min(R, mid); i++) { ll val = f[j - 1][i - 1] + calc(i, mid); if (val <= f[j][mid]) f[j][mid] = val, pos = i; } solve(j, l, mid - 1, L, pos); solve(j, mid + 1, r, pos, R); } int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", a + i); memset(f, 0x3f, sizeof(f)); f[0][0] = 0; nowl = 1, nowr = 0; for (int i = 1; i <= k; i++) solve(i, 1, n, 1, n); printf("%lld\n", f[k][n]); return 0; }
20220726
CF1707C DFS Trees
思路:copied from https://www.cnblogs.com/ruierqwq/p/CF-1707C.html。
因为所有边权两两不同,所以 MST 是唯一的,我们把 MST 上的边标记出来。
我们知道对图进行 DFS 后,只有树边和返祖边两类边。要使得 MST 上的边均为树边,则不在 MST 上的边只能为返祖边。也就是说,不在 MST 上的边在当前根下必须是祖先后代关系。
至此,原问题转化为:判断每个节点作为根时,所有非 MST 边的两个点是否都是祖先后代关系。
我们可以随便找一个点当根,把每条非 MST 边两侧子树每个点的值加一(只有在这些点当根时才是祖先后代关系),然后判断每个点的值是否等于
算法:MST、DFS 树、树上差分。
技巧:无向图的 DFS 树上只有树边和返祖边、考虑每条边对答案的贡献。
想到了的:MST。
没想到的:无向图的 DFS 树上只有树边和返祖边。
代码
#include <algorithm> #include <cstdio> #include <string> using namespace std; const int N = 1e5, M = 2e5, L = 20; struct Edge { int to, nxt; } e[N * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v) { e[++tote] = {v, head[u]}; head[u] = tote; } struct Edge2 { int u, v, w; }; int n, m; Edge2 E[M + 10]; int f[N + 10], dep[N + 10], fa[N + 10][L + 5]; int cnt[N + 10]; bool inMST[M + 10]; int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); } void kruskal() { sort(E + 1, E + m + 1, [](const Edge2 &lhs, const Edge2 &rhs) { return lhs.w < rhs.w; }); for (int i = 1; i <= n; i++) f[i] = i; for (int i = 1; i <= m; i++) { int u = E[i].u, v = E[i].v; if (find(u) != find(v)) { inMST[i] = 1; f[find(u)] = find(v); addEdge(u, v), addEdge(v, u); } } } void dfs1(int u, int ffa) { fa[u][0] = ffa, dep[u] = dep[ffa] + 1; for (int i = 1; i <= L; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v == ffa) continue; dfs1(v, u); } } int lca(int u, int v) { if (dep[u] < dep[v]) swap(u, v); for (int i = L; i >= 0; i--) if (dep[fa[u][i]] >= dep[v]) u = fa[u][i]; if (u == v) return u; for (int i = L; i >= 0; i--) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; return fa[u][0]; } int jump(int u, int v) { for (int i = L; i >= 0; i--) if (dep[fa[u][i]] > dep[v]) u = fa[u][i]; return u; } void dfs2(int u, int ffa) { cnt[u] += cnt[ffa]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v == ffa) continue; dfs2(v, u); } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) scanf("%d%d", &E[i].u, &E[i].v), E[i].w = i; kruskal(); dfs1(1, 0); for (int i = 1; i <= m; i++) if (!inMST[i]) { int u = E[i].u, v = E[i].v, l = lca(u, v); if (dep[u] > dep[v]) swap(u, v); if (u == l) cnt[1]++, cnt[v]++, cnt[jump(v, u)]--; else cnt[u]++, cnt[v]++; } dfs2(1, 0); for (int i = 1; i <= n; i++) printf("%d", cnt[i] == m - n + 1); puts(""); return 0; }
CF1111E Tree
思路:
记询问中涉及到的结点为“关键点”。
首先对于每组询问,我们可以做一个 dp:设
这里为了保证 dp 没有后效性,必须保证父亲结点在孩子结点之前被计算,也就是说我们按照
至此本题已经解决了一半。我们现在的问题是:在根结点
对于链上和问题,我们可以使用树上差分的思想处理。于是只需求
考虑一个关键点
子树加单点查是一个经典问题。子树在树的 DFS 序上是一个连续段,于是我们只需在 DFS 序上区间加单点查,树状数组维护差分序列即可。
算法:dp、数据结构。
技巧:分组问题考虑类似第二类斯特林数的 dp。
想到了的:无。
没想到的:都没想到。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 1e5, M = 300, L = 20, P = int(1e9) + 7; struct Edge { int to, nxt; } e[N * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v) { e[++tote] = {v, head[u]}; head[u] = tote; } int n, q, id[N + 10], cnt[N + 10], f[N + 10][M + 5]; int dfn[N + 10], siz[N + 10], dfncnt, dep[N + 10], fa[N + 10][L + 5]; int c[N + 10]; #define lowbit(x) (x & (-x)) void modify(int p, int x) { for (; p <= n; p += lowbit(p)) c[p] += x; } int query(int p) { int res = 0; for (; p; p -= lowbit(p)) res += c[p]; return res; } #undef lowbit void dfs(int u, int ffa) { dfn[u] = ++dfncnt; siz[u] = 1; fa[u][0] = ffa; dep[u] = dep[ffa] + 1; for (int i = 1; i <= L; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v == ffa) continue; dfs(v, u); siz[u] += siz[v]; } } int lca(int u, int v) { if (dep[u] < dep[v]) swap(u, v); for (int i = L; i >= 0; i--) if (dep[fa[u][i]] >= dep[v]) u = fa[u][i]; if (u == v) return u; for (int i = L; i >= 0; i--) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; return fa[u][0]; } int query(int u, int v) { int l = lca(u, v); return query(dfn[u]) + query(dfn[v]) - query(dfn[l]) - query(dfn[fa[l][0]]); } int main() { scanf("%d%d", &n, &q); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); addEdge(u, v), addEdge(v, u); } dfs(1, 0); while (q--) { int k, m, r; scanf("%d%d%d", &k, &m, &r); for (int i = 1; i <= k; i++) { scanf("%d", id + i); modify(dfn[id[i]], 1), modify(dfn[id[i]] + siz[id[i]], -1); } for (int i = 1; i <= k; i++) cnt[id[i]] = query(id[i], r); sort(id + 1, id + k + 1, [&](int lhs, int rhs) { return cnt[lhs] < cnt[rhs]; }); f[0][0] = 1; for (int i = 1; i <= k; i++) for (int j = 1; j <= m; j++) f[i][j] = (f[i - 1][j - 1] + 1LL * f[i - 1][j] * (j - cnt[id[i]] + 1) % P) % P; int ans = 0; for (int i = 1; i <= m; i++) (ans += f[k][i]) %= P; printf("%d\n", ans); for (int i = 1; i <= k; i++) modify(dfn[id[i]], -1), modify(dfn[id[i]] + siz[id[i]], 1); } return 0; }
20220727
CF685B Kay and Snowflake
思路:树的重心的性质:树的重心在根结点到它重儿子子树重心的路径上。计算
算法:树的重心。
技巧:树的重心在根结点到它重儿子子树重心的路径上。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 3e5; struct Edge { int to, nxt; } e[N * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v) { e[++tote] = {v, head[u]}; head[u] = tote; } int n, m, fa[N + 10], ans[N + 10], siz[N + 10], son[N + 10]; void dfs(int u, int ffa) { siz[u] = 1; bool flg = 0; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v == ffa) continue; flg = 1; dfs(v, u); siz[u] += siz[v]; if (siz[v] > siz[son[u]]) son[u] = v; } if (!flg) ans[u] = u; else { ans[u] = ans[son[u]]; while (ans[u] != u) { if (siz[son[ans[u]]] <= siz[u] / 2 && siz[u] - siz[ans[u]] <= siz[u] / 2) break; ans[u] = fa[ans[u]]; } } } int main() { scanf("%d%d", &n, &m); for (int i = 2; i <= n; i++) { scanf("%d", fa + i); addEdge(fa[i], i), addEdge(i, fa[i]); } dfs(1, 0); while (m--) { int x; scanf("%d", &x); printf("%d\n", ans[x]); } return 0; }
CF1406C Link Cut Centroids
思路:一棵树最多有两个重心。如果有一个那么随便做,如果有两个我们可以直接从一个重心的子树里找一个叶子接到另一个重心上。
算法:树的重心。
技巧:构造。
想到了的:都想到的。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <vector> #include <tuple> using namespace std; const int N = 1e5; struct Edge { int to, nxt; } e[N * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v) { e[++tote] = {v, head[u]}; head[u] = tote; } int n, siz[N + 10]; vector<int> cent; void dfs1(int u, int fa) { siz[u] = 1; int mx = 0; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v == fa) continue; dfs1(v, u); siz[u] += siz[v]; mx = max(siz[v], mx); } mx = max(mx, n - siz[u]); if (mx <= n / 2) cent.push_back(u); } pair<int, int> dfs2(int u, int fa) { for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v == fa) continue; return dfs2(v, u); } return {u, fa}; } void mian() { scanf("%d", &n); int uu, vv; for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); addEdge(v, u), addEdge(u, v); uu = u, vv = v; } dfs1(1, 0); if (int(cent.size()) == 1) printf("%d %d\n%d %d\n", uu, vv, uu, vv); else { tie(uu, vv) = dfs2(cent[0], cent[1]); printf("%d %d\n%d %d\n", uu, vv, uu, cent[1]); } } int main() { int T; scanf("%d", &T); while (T--) { for (int i = 1; i <= n; i++) head[i] = siz[i] = 0; tote = 0; cent.clear(); mian(); } return 0; }
20220729
CF1152C Neko does Maths
思路:
于是我们就应该让
算法:简单数论、贪心。
技巧:
想到的了:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <cmath> using namespace std; typedef long long ll; ll calc(ll a, ll b, ll k) { return (a + k) / __gcd(a + k, b + k) * (b + k); } int main() { int a, b; scanf("%d%d", &a, &b); if (a == b) puts("0"); else { if (a < b) swap(a, b); int d = a - b; // gcd(a - b, a + k) = a - b ll ans = 1LL * ((a - 1) / (a - b)) * (a - b) - b, mn = calc(a, b, ans); for (int i = 1; i <= int(sqrt(d)); i++) if (d % i == 0) { // gcd(a - b, a + k) = a + k ll now = d / i - b; if (now >= 0 && calc(a, b, now) < mn) { mn = calc(a, b, now); ans = now; } else if (calc(a, b, now) == mn) ans = min(ans, now); now = i - b; if (now >= 0 && calc(a, b, now) < mn) { mn = calc(a, b, now); ans = now; } else if (calc(a, b, now) == mn) ans = min(ans, now); } printf("%lld\n", ans); } return 0; }
20220805
CF1176D Recover it!
思路:贪心地从大到小匹配即可。
算法:简单数论、贪心。
技巧:按顺序考虑。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <functional> #include <vector> using namespace std; const int N = 4e5, M = 3e6; int n, a[N + 10], b[N + 10], prm[N + 10], totp, id[M + 10]; bool notPrm[M + 10], vis[N + 10]; vector<int> pos[M + 10]; void sieve() { notPrm[1] = 1; for (int i = 2; i <= M; i++) { if (!notPrm[i]) prm[++totp] = i, id[i] = totp; for (int j = 1; j <= totp && i * prm[j] <= M; j++) { notPrm[i * prm[j]] = 1; if (i % prm[j] == 0) break; } } } int main() { sieve(); scanf("%d", &n); for (int i = 1; i <= 2 * n; i++) scanf("%d", b + i); sort(b + 1, b + 2 * n + 1, greater<int>()); for (int i = 2 * n; i >= 1; i--) pos[b[i]].push_back(i); int tota = 0; for (int i = 1; i <= 2 * n; i++) { if (vis[i]) continue; if (!notPrm[b[i]]) { a[++tota] = id[b[i]]; vis[pos[b[i]].back()] = 1; pos[b[i]].pop_back(); vis[pos[id[b[i]]].back()] = 1; pos[id[b[i]]].pop_back(); } else { for (int j = 1; j <= totp; j++) if (b[i] % prm[j] == 0) { a[++tota] = b[i]; vis[pos[b[i] / prm[j]].back()] = 1; pos[b[i] / prm[j]].pop_back(); vis[pos[b[i]].back()] = 1; pos[b[i]].pop_back(); break; } } } for (int i = 1; i <= n; i++) printf("%d%c", a[i], " \n"[i == n]); return 0; }
20220810
P4301 [CQOI2013] 新Nim游戏
思路:先手要胜利,则必须让后手无论拿掉哪几堆都弄不出异或和为
算法:博弈论、线性基、贪心。
技巧:按顺序考虑、博弈论问题考虑线性基。
想到了的:线性基。
没想到的:贪心。
代码
#include <algorithm> #include <cstdio> #include <functional> using namespace std; typedef long long ll; const int N = 100, K = 30; int n, a[N + 10], p[K + 10]; ll ans; bool insert(int x) { for (int i = K; i >= 0; i--) { if (!((x >> i) & 1)) continue; if (!p[i]) return p[i] = x, 1; x ^= p[i]; } return 0; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); sort(a + 1, a + n + 1, greater<int>()); for (int i = 1; i <= n; i++) if (!insert(a[i])) ans += a[i]; printf("%lld\n", ans); return 0; }
CF1100F Ivan and Burgers
思路:离线后使用时间戳线性基。
算法:时间戳线性基。
技巧:时间戳。
想到了的:(模板题)
没想到的:(模板题)
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 5e5, L = 20; struct Query { int l, r, id; }; int n, m, a[N + 10], p[L + 10], stp[N + 10]; Query q[N + 10]; int ans[N + 10]; void insert(int x, int y) { for (int i = L; i >= 0; i--) { if (!((x >> i) & 1)) continue; if (!p[i]) return p[i] = x, stp[i] = y, void(); if (y > stp[i]) swap(x, p[i]), swap(y, stp[i]); x ^= p[i]; } } int query(int x) { int res = 0; for (int i = L; i >= 0; i--) if ((res ^ p[i]) > res && stp[i] >= x) res ^= p[i]; return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); scanf("%d", &m); for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i; sort(q + 1, q + m + 1, [](const Query &lhs, const Query &rhs) { return lhs.r < rhs.r; }); for (int i = 1, j = 1; i <= m; i++) { while (j <= q[i].r) { insert(a[j], j); j++; } ans[q[i].id] = query(q[i].l); } for (int i = 1; i <= m; i++) printf("%d\n", ans[i]); return 0; }
20220811
P4151 [WC2011]最大XOR和路径
思路:由异或的性质,一条路径可以等价为一个
算法:线性基。
技巧:异或性质。
想到了的:(例题)
没想到的:(例题)
代码
#include <algorithm> #include <cstdio> using namespace std; typedef long long ll; const int N = 5e4, M = 1e5, L = 60; struct Edge { int to, nxt; ll w; } e[M * 2 + 10]; int head[N + 10], tote; void addEdge(int u, int v, ll w) { e[++tote] = {v, head[u], w}; head[u] = tote; } int n, m; ll p[L + 10], dis[N + 10]; bool vis[N + 10]; void insert(ll x) { for (int i = L; i >= 0; i--) { if (!((x >> i) & 1)) continue; if (!p[i]) return p[i] = x, void(); x ^= p[i]; } } ll query(ll x) { ll res = x; for (int i = L; i >= 0; i--) res = max(res, res ^ p[i]); return res; } void dfs(int u, ll now) { dis[u] = now; vis[u] = 1; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (!vis[v]) dfs(v, now ^ e[i].w); else insert(dis[u] ^ dis[v] ^ e[i].w); } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) { int u, v; ll w; scanf("%d%d%lld", &u, &v, &w); addEdge(u, v, w), addEdge(v, u, w); } dfs(1, 0); printf("%lld\n", query(dis[n])); return 0; }
20220826
CF1718A Burenka and Traditions
思路:发现当
算法:位运算、dp。
技巧:找性质猜结论。
想到了的:无。
没想到的:都没想到。
代码
#include <algorithm> #include <cstdio> #include <map> using namespace std; const int N = 1e5; int n, a[N + 10], pre[N + 10], f[N + 10]; void mian() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i), pre[i] = pre[i - 1] ^ a[i]; map<int, int> mp; mp[0] = 0; for (int i = 1; i <= n; i++) { f[i] = f[i - 1] + 1; if (mp.find(pre[i]) != mp.end()) f[i] = min(f[i], f[mp[pre[i]]] + i - mp[pre[i]] - 1); mp[pre[i]] = i; } printf("%d\n", f[n]); } int main() { int T; scanf("%d", &T); while (T--) mian(); return 0; }
20220827
P2659 美丽的序列
思路:枚举每个
算法:单调栈。
技巧:单调栈。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> using namespace std; typedef long long ll; const int N = 2e6; int n, a[N + 10], stk[N + 10], L[N + 10], R[N + 10], top; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); for (int i = 1; i <= n; i++) { while (top && a[stk[top]] >= a[i]) top--; L[i] = stk[top]; stk[++top] = i; } top = 0; for (int i = n; i >= 1; i--) { while (top && a[stk[top]] >= a[i]) top--; R[i] = top ? stk[top] : n + 1; stk[++top] = i; } ll ans = 0; for (int i = 1; i <= n; i++) ans = max(ans, 1LL * a[i] * (R[i] - L[i] - 1)); printf("%lld\n", ans); return 0; }
CF1718B Fibonacci Strings
思路:显然我们要从大的 Fibonacci 数开始考虑,并且用尽量大的
算法:贪心。
技巧:贪心地按从大到小(或从小到大)顺序考虑。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> #include <queue> using namespace std; typedef long long ll; const int N = 100; int n, a[N + 10], totf; ll f[N + 10], sumf[N + 10]; void mian() { scanf("%d", &n); priority_queue<int> q; ll sum = 0; for (int i = 1; i <= n; i++) scanf("%d", a + i), q.push(a[i]), sum += a[i]; int pos = -1; for (int i = 1; i <= totf; i++) if (sumf[i] == sum) { pos = i; break; } if (pos == -1) return puts("NO"), void(); int lst = 0; for (int i = pos; i >= 1; i--) { if (q.empty()) return puts("NO"), void(); int x = q.top(); q.pop(); if (lst) q.push(lst); if (x < f[i]) return puts("NO"), void(); lst = x - f[i]; } puts(q.empty() ? "YES" : "NO"); } int main() { f[++totf] = 1, f[++totf] = 1; while (f[totf] <= int(1e9)) { totf++; f[totf] = f[totf - 1] + f[totf - 2]; } for (int i = 1; i <= totf; i++) sumf[i] = sumf[i - 1] + f[i]; int T; scanf("%d", &T); while (T--) mian(); return 0; }
20220830
AT4168 [ARC100C] Or Plus Max
思路:记
我们考虑求
上述式子成立是由于或运算的性质。或运算是不减的,并且
那么剩下的问题可以用高维前缀和维护
算法:位运算、高维前缀和(SOSdp)。
技巧:位运算转化。
想到的了:高维前缀和(SOSdp)。
没想到的:位运算转化。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 5e5; struct Node { int mx1, mx2; }; int n; Node f[N + 10]; Node merge(const Node &A, const Node &B) { int tmp[] = {A.mx1, A.mx2, B.mx1, B.mx2}; sort(tmp, tmp + 4); return {tmp[3], tmp[2]}; } int main() { scanf("%d", &n); for (int i = 0; i < (1 << n); i++) scanf("%d", &f[i].mx1), f[i].mx2 = -0x3f3f3f3f; for (int j = 0; j < n; j++) for (int i = 0; i < (1 << n); i++) if ((i >> j) & 1) f[i] = merge(f[i], f[i ^ (1 << j)]); int ans = 0; for (int i = 1; i < (1 << n); i++) { ans = max(ans, f[i].mx1 + f[i].mx2); printf("%d\n", ans); } return 0; }
P7706 「Wdsr-2.7」文文的摄影布置
思路:显然用线段树做。考虑合并的时候怎么合并,
算法:线段树。
技巧:考虑全在左边,全在右边,跨过了左右两边三种情况。
想到了的:都想到了。
没想到的:无。
代码
#include <algorithm> #include <cstdio> using namespace std; const int N = 5e5; struct Node { int res, mxa, mnb, mxlr, mxrl; }; Node t[N * 4 + 10]; int n, m, a[N + 10], b[N + 10]; #define ls(x) (x << 1) #define rs(x) (x << 1 | 1) Node pushUp(const Node &L, const Node &R) { Node res; res.res = max({L.res, R.res, L.mxlr + R.mxa, L.mxa + R.mxrl}); res.mxa = max(L.mxa, R.mxa); res.mnb = min(L.mnb, R.mnb); res.mxlr = max({L.mxlr, R.mxlr, L.mxa - R.mnb}); res.mxrl = max({L.mxrl, R.mxrl, R.mxa - L.mnb}); return res; } void build(int i, int l, int r) { if (l == r) return t[i].mxa = a[l], t[i].mnb = b[l], t[i].res = t[i].mxlr = t[i].mxrl = -0x3f3f3f3f, void(); int mid = (l + r) >> 1; build(ls(i), l, mid); build(rs(i), mid + 1, r); t[i] = pushUp(t[ls(i)], t[rs(i)]); } void modify(int i, int l, int r, int p, int aa, int bb) { if (l == r) return t[i].mxa = aa, t[i].mnb = bb, void(); int mid = (l + r) >> 1; if (p <= mid) modify(ls(i), l, mid, p, aa, bb); else modify(rs(i), mid + 1, r, p, aa, bb); t[i] = pushUp(t[ls(i)], t[rs(i)]); } Node query(int i, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return t[i]; int mid = (l + r) >> 1; if (qr <= mid) return query(ls(i), l, mid, ql, qr); if (ql > mid) return query(rs(i), mid + 1, r, ql, qr); return pushUp(query(ls(i), l, mid, ql, qr), query(rs(i), mid + 1, r, ql, qr)); } #undef ls #undef rs int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", a + i); for (int i = 1; i <= n; i++) scanf("%d", b + i); build(1, 1, n); while (m--) { int opt, x, y; scanf("%d%d%d", &opt, &x, &y); if (opt == 1) { modify(1, 1, n, x, y, b[x]); a[x] = y; } if (opt == 2) { modify(1, 1, n, x, a[x], y); b[x] = y; } if (opt == 3) printf("%d\n", query(1, 1, n, x, y).res); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix