博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Comet OJ - Contest #5 简要题解
阅读量:4700 次
发布时间:2019-06-09

本文共 12905 字,大约阅读时间需要 43 分钟。

好久没更博了,还是象征性地更一次。

依然延续了简要题解的风格。

题目链接

题解

A. 迫真字符串

\(s_i\) 表示数字 \(i\) 出现的次数,答案为 \(\min\{\lfloor\frac{s_1}{3}\rfloor, \lfloor\frac{s_4}{2}\rfloor, s_5\}\)

#include
using namespace std;int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); string s; cin >> s; int a = 0, b = 0, c = 0, n = s.length(); for (int i = 0; i < n; ++i) { if (s[i] == '1') { ++a; } else if (s[i] == '4') { ++b; } else if (s[i] == '5') { ++c; } } cout << min(min(a / 3, b / 2), c) << '\n'; return 0;}

B. 迫真数论

暴力。

#include
using namespace std;int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int tt; long long n; cin >> tt; while (tt--) { cin >> n; int answer = 0; auto f = [&] (int x) { int result = 0; while (x) { result += x % 10; x /= 10; } return result; }; for (int i = 1; i <= 200; ++i) { if (n % i == 0 && f(i) == (i >> 1)) { ++answer; } } cout << answer << '\n'; } return 0;}

C. 迫真小游戏

贪心。

#include
using namespace std;const int N = 567890;int n, a[N], depth[N];vector
adj[N], nodes[N];bool visit[N];void dfs(int x, int f) { nodes[depth[x] = depth[f] + 1].push_back(x); for (auto y : adj[x]) { if (y != f) { dfs(y, x); } }}int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n; for (int i = 1, x, y; i < n; ++i) { cin >> x >> y; adj[x].push_back(y); adj[y].push_back(x); } dfs(1, 0); for (int i = 1; i <= n; ++i) { cin >> a[i]; } multiset
s; priority_queue
, greater
> q; for (int i = 2; i <= n; ++i) { s.insert(a[i]); } cout << 1 << " \n"[n == 1]; int j = 1, tt = 1; while (tt != n) { while (s.size() && j < *s.begin()) { ++j; for (auto x : nodes[j]) { q.push(x); } } int x = q.top(); s.erase(s.find(a[x])); q.pop(); cout << x << " \n"[++tt == n]; } return 0;}

D. 迫真图论

假设 \(n, m\) 同阶。将点按度数大小是否超过 \(\sqrt n\) 分类,记为大点和小点,那么小点的度数不超过 \(\sqrt n\),大点的总数量不超过 \(O(\sqrt n)\),这样就可以暴力做了。对于每个大点,用一棵 trie 维护与其相邻的小点间的边的信息;对于所有小点与小点间的边、大点与大点间的边的信息,可以用一棵全局的树状数组维护。大点的权值对 trie 的影响可以用一个tag标记记录,剩下的修改与查询操作就比较显然了。

在代码实现中,点按度数大小分类的阈值可以设得比 \(\sqrt n\) 稍大一些。

#include
using namespace std;const int N = 1 << 18, sq = 2000, mod = 998244353;int n, m, q, tt, degree[N], a[N], x[N], y[N], z[N], ch[N * 100][2], root[N], tag[N], now_tag;vector
> adj[N], sadj[N];long long sum[N * 100];bool type[N];class bit { long long a[N]; public: bit() { memset(a, 0, sizeof a); } void add(int x, int y) { if (!x) { a[0] += y; return; } while (x < N) { a[x] += y; x += x & -x; } } long long sum(int x) { long long result = a[0]; while (x) { result += a[x]; x -= x & -x; } return result; }} tree;void insert(int& x, int d, int y, int z, int now_tag) { if (!x) { x = ++tt; } sum[x] += z; if (!~d) { return; } insert(ch[x][(y >> d & 1) ^ (now_tag >> d & 1)], d - 1, y, z, now_tag);}long long query(int x, int d, int y, int now_tag) { if (!x) { return 0; } if (!~d) { return sum[x]; } if (y >> d & 1) { if (now_tag >> d & 1) { return sum[ch[x][1]] + query(ch[x][0], d - 1, y, now_tag); } else { return sum[ch[x][0]] + query(ch[x][1], d - 1, y, now_tag); } } else { if (now_tag >> d & 1) { return query(ch[x][1], d - 1, y, now_tag); } else { return query(ch[x][0], d - 1, y, now_tag); } }}int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m >> q; for (int i = 1; i <= n; ++i) { cin >> a[i]; } for (int i = 1; i <= m; ++i) { cin >> x[i] >> y[i] >> z[i]; adj[x[i]].emplace_back(y[i], i); adj[y[i]].emplace_back(x[i], i); ++degree[x[i]]; ++degree[y[i]]; } for (int i = 1; i <= n; ++i) { if (degree[i] <= sq) { type[i] = false; } else { type[i] = true; } } vector
snodes; for (int i = 1; i <= n; ++i) { if (type[i]) { tag[i] = a[i]; snodes.push_back(i); for (auto p : adj[i]) { if (type[p.first]) { sadj[i].push_back(p); } } } } for (int i = 1; i <= n; ++i) { for (auto p : adj[i]) { int j = p.first; if (type[i] == type[j] && i < j) { tree.add(a[i] ^ a[j], z[p.second]); } else if (type[i] && !type[j]) { insert(root[i], 16, a[i] ^ a[j], z[p.second], tag[i]); } } } for (int i = 1, op, u, v; i <= q; ++i) { cin >> op >> u >> v; if (op == 1) { if (!type[u]) { for (auto p : adj[u]) { if (!type[p.first]) { tree.add(a[u] ^ a[p.first], -z[p.second]); tree.add(v ^ a[p.first], z[p.second]); } else { insert(root[p.first], 16, a[u] ^ a[p.first], -z[p.second], tag[p.first]); insert(root[p.first], 16, v ^ a[p.first], z[p.second], tag[p.first]); } } } else { tag[u] ^= a[u] ^ v; for (auto p : sadj[u]) { tree.add(a[u] ^ a[p.first], -z[p.second]); tree.add(v ^ a[p.first], z[p.second]); } } a[u] = v; } else if (op == 2) { int s = x[u], t = y[u]; if (type[s] == type[t]) { tree.add(a[s] ^ a[t], -z[u]); tree.add(a[s] ^ a[t], v); } else { if (type[t]) { swap(s, t); } insert(root[s], 16, a[s] ^ a[t], -z[u], tag[s]); insert(root[s], 16, a[s] ^ a[t], v, tag[s]); } z[u] = v; } else { --u; long long answer = tree.sum(v) - (~u ? tree.sum(u) : 0); for (auto x : snodes) { answer += query(root[x], 16, v, tag[x]) - (~u ? query(root[x], 16, u, tag[x]) : 0); } cout << (answer % mod) << '\n'; } } return 0;}

E. 迫真大游戏

先只考虑 \(1\) 号分身。定义 \(f_i\) 表示当前还剩 \(i\) 个分身(必然包含 \(1\) 号分身),\(1\) 号分身最后消失的概率。那么有 \(f_i = \sum_\limits{j = 1}^i \binom{i - 1}{j - 1} (1 - p)^{j}p^{i- j}f_j\)\(f_1 = 1\),可以用分治 NTT 在 \(O(n \log^2 n)\) 的时间内求出所有 \(f_i\),那么 \(1\) 号分身的答案即为 \(f_n\)

现在考虑求其他分身的答案。为了让 \(x\) 号分身的答案也能用 \(f_i\) 求出,我们可以直接枚举前 \(x - 1\) 个人的消失情况,然后乘以对应的方案数和概率,发现又是一个卷积的形式,于是再做一次 NTT 即可。

#include
using namespace std;const int N = 567890, mod = 998244353, root = 3;void add(int& x, int y) { x += y; if (x >= mod) { x -= mod; }}int mul(int x, int y) { return (long long) x * y % mod;}int qpow(int x, int y) { int result = 1; for (; y; y >>= 1, x = mul(x, x)) { if (y & 1) { result = mul(result, x); } } return result;}int n, a, b, p, rev[N], f[N], fac[N], ifac[N], inv[N];void dft(vector
& buffer, bool inv = false) { int n = buffer.size(); for (int i = 0; i < n; ++i) { if (i < rev[i]) { swap(buffer[i], buffer[rev[i]]); } } for (int i = 1; i < n; i <<= 1) { int x = qpow(root, inv ? mod - 1 - (mod - 1) / (i << 1) : (mod - 1) / (i << 1)); for (int j = 0; j < n; j += i << 1) { int y = 1; for (int k = 0; k < i; ++k, y = mul(y, x)) { int p = buffer[j + k], q = mul(y, buffer[i + j + k]); buffer[j + k] = (p + q) % mod; buffer[i + j + k] = (p - q + mod) % mod; } } } if (inv) { int x = qpow(n, mod - 2); for (int i = 0; i < n; ++i) { buffer[i] = mul(buffer[i], x); } }}vector
pmul(vector
x, vector
y) { int n = x.size() + y.size() - 1, len = 0; for (; (1 << len) < n; ++len); for (int i = 0; i < (1 << len); ++i) { rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << len - 1); } x.resize(1 << len); y.resize(1 << len); dft(x); dft(y); for (int i = 0; i < (1 << len); ++i) { x[i] = mul(x[i], y[i]); } dft(x, true); x.resize(n); return x;}void solve(int l, int r) { if (l == r) { if (l == 1) { f[1] = 1; } else { f[l] = mul(f[l], fac[l - 1]); f[l] = mul(f[l], qpow(1 - qpow(1 - p + mod, l) + mod, mod - 2)); } } else { int mid = l + r >> 1; solve(l, mid); vector
foo(mid - l + 1), bar(r - l); for (int i = l; i <= mid; ++i) { foo[i - l] = mul(f[i], mul(qpow(1 - p + mod, i), ifac[i - 1])); } for (int i = 1; i <= r - l; ++i) { bar[i - 1] = mul(qpow(p, i), ifac[i]); } foo = pmul(foo, bar); for (int i = mid + 1; i <= r; ++i) { add(f[i], foo[i - l - 1]); } solve(mid + 1, r); }}int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> a >> b; p = mul(a, qpow(b, mod - 2)); fac[0] = ifac[0] = inv[1] = fac[1] = ifac[1] = 1; for (int i = 2; i <= n; ++i) { inv[i] = mul(mod - mod / i, inv[mod % i]); fac[i] = mul(fac[i - 1], i); ifac[i] = mul(ifac[i - 1], inv[i]); } solve(1, n); vector
foo(n), bar(n); for (int i = 0; i < n; ++i) { foo[i] = mul(f[n - i], mul(qpow(p, i), ifac[i])); bar[i] = mul(qpow(1 - p + mod, i), ifac[i]); } foo = pmul(foo, bar); for (int i = 0; i < n; ++i) { cout << mul(foo[i], fac[i]) << '\n'; } return 0;}

F. 迫真树

二分答案 \(k\) 后通过做 dp 来判断是否存在合法方案。假设整棵树以 \(1\) 为根,设 \(f_{i, j}\) 表示 \(i\) 号点往子树内延伸的最长链长度为 \(j\),且子树合法(即子树内最长链不超过 \(k\))的最小代价(\(f_{i, 0}\) 则表示不选 \(i\) 点的最小代价),那么转移比较显然。注意到 dp 状态的第二维与点往下延伸的最长链长度有关,那么考虑用长链剖分,对 dp 转移分情况讨论后发现需要用到一段区间内的最优 dp 值,用线段树维护即可。

#include
using namespace std;const int N = 123456;const long long llinf = 1e18;int n, m, tt, a[N], heavy[N], dfn[N], maxd[N];vector
adj[N];long long dp0[N];class segment_t { long long a[N << 2], tag[N << 2]; public: segment_t() { fill(a, a + (N << 2), llinf); memset(tag, 0, sizeof tag); } void mark(int x, long long y) { tag[x] += y; a[x] += y; } void push(int x) { if (tag[x]) { mark(x << 1, tag[x]); mark(x << 1 | 1, tag[x]); tag[x] = 0; } } void modify(int l, int r, int x, int ql, int qr, long long y) { if (ql <= l && r <= qr) { mark(x, y); } else { int mid = l + r >> 1; push(x); if (ql <= mid) { modify(l, mid, x << 1, ql, qr, y); } if (qr > mid) { modify(mid + 1, r, x << 1 | 1, ql, qr, y); } a[x] = min(a[x << 1], a[x << 1 | 1]); } } void modify(int l, int r, int x, int p, long long y) { if (l == r) { a[x] = min(a[x], y); } else { int mid = l + r >> 1; push(x); if (p <= mid) { modify(l, mid, x << 1, p, y); } else { modify(mid + 1, r, x << 1 | 1, p, y); } a[x] = min(a[x << 1], a[x << 1 | 1]); } } long long query(int l, int r, int x, int ql, int qr) { if (ql <= l && r <= qr) { return a[x]; } else { int mid = l + r >> 1; long long result = llinf; push(x); if (ql <= mid) { result = min(result, query(l, mid, x << 1, ql, qr)); } if (qr > mid) { result = min(result, query(mid + 1, r, x << 1 | 1, ql, qr)); } return result; } }};void dfs(int x, int f) { maxd[x] = -1; for (auto y : adj[x]) { if (y != f) { dfs(y, x); if (maxd[y] > maxd[x]) { heavy[x] = y; maxd[x] = maxd[y]; } } } ++maxd[x];}bool check(int k) { segment_t tree; auto dp = [&] (int x, int y) { return !y ? dp0[x] : tree.query(1, n, 1, dfn[x] + y - 1, dfn[x] + y - 1); }; function
dfs = [&] (int x, int f) { dp0[x] = a[x]; dfn[x] = ++tt; if (maxd[x]) { dfs(heavy[x], x); dp0[x] += min(dp(heavy[x], 0), tree.query(1, n, 1, dfn[heavy[x]], dfn[heavy[x]] + min(maxd[heavy[x]], k - 1))); tree.modify(1, n, 1, dfn[x], dp(heavy[x], 0)); } else { tree.modify(1, n, 1, dfn[x], 0); } for (auto y : adj[x]) { if (y != f && y != heavy[x]) { dfs(y, x); dp0[x] += min(dp(y, 0), tree.query(1, n, 1, dfn[y], dfn[y] + min(maxd[y], k - 1))); vector
foo; vector
> bar; for (int j = 1; j <= min(maxd[y] + 2, k); ++j) { int l = 1, r = min(j, k - j + 1); if (l > r) { break; } long long t = dp(y, j - 1); foo.push_back(t + tree.query(1, n, 1, dfn[x] + l - 1, dfn[x] + r - 1)); if (!bar.size() || (bar.size() && t < bar.back().second)) { bar.emplace_back(j - 1, t); } } long long last = 0; for (auto p : bar) { int l = p.first + 1, r = min(maxd[x] + 1, k - p.first); if (l <= r) { tree.modify(1, n, 1, dfn[x] + l - 1, dfn[x] + r - 1, p.second - last); last = p.second; } } for (int i = 0; i < foo.size(); ++i) { tree.modify(1, n, 1, dfn[x] + i, foo[i]); } } } }; tt = 0; dfs(1, 0); return min(dp(1, 0), tree.query(1, n, 1, dfn[1], dfn[1] + min(maxd[1], k - 1))) <= m;}int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m; long long all = 0; for (int i = 1; i <= n; ++i) { cin >> a[i]; all += a[i]; } if (all <= m) { cout << 0 << '\n'; exit(0); } for (int i = 1, x, y; i < n; ++i) { cin >> x >> y; adj[x].push_back(y); adj[y].push_back(x); } dfs(1, 0); int l = 1, r = n; while (l != r) { int mid = l + r >> 1; if (check(mid)) { r = mid; } else { l = mid + 1; } } cout << l << '\n'; return 0;}

转载于:https://www.cnblogs.com/ImagineC/p/11045892.html

你可能感兴趣的文章
力扣——N叉树的后序遍历
查看>>
C++ namespace命名空间
查看>>
用Hadoop构建电影推荐系统
查看>>
automake连载---关于两个文件configure.in和Makefile.am的编写
查看>>
JQuery选择器中含有冒号的ID处理差异的分析
查看>>
分享:一款前端布局工具(alloydesigner)
查看>>
Application对象
查看>>
python爬虫实战(3)--图片下载器
查看>>
win7系统中开启wifi热点
查看>>
干货|最详尽的神经网络基础
查看>>
翻转字符串和左旋转字符串
查看>>
wampserver配置多站点
查看>>
找不到请求的 .Net Framework Data Provider。可能没有安装
查看>>
实验室管理系统(SQL+VS)
查看>>
C# protogen 处理protobuf生成cs文件
查看>>
oracle_SQL 实验查询及删除重复记录 依据条件 (row)
查看>>
SSM框架搭建
查看>>
[UE4]蓝图比C++慢10倍,是吗?
查看>>
使用IdleTest进行TDD单元测试驱动开发演练(1)
查看>>
零基础入门深度学习(2) - 线性单元和梯度下降
查看>>