2023牛客暑期多校训练营6 ABCEG
A
题解
方法一
知识点:并查集,树形dp,背包dp。
因为需要路径中的最大值,因此考虑按边权从小到大加入图中,保证通过这条边产生贡献的点对已经全部出现。
在加边的同时进行树上背包,答案存在集合根节点里即可。
树上背包需要用到上下界限制的转移优化,能将复杂度从 降到 ,基本思想是每个点对只在LCA处贡献一次。
时间复杂度
空间复杂度
方法二
知识点:Kruskal重构树,树形dp,背包dp。
对原图进行最小生成树性质的Kruskal重构,任意两点的LCA点权为路径最大值。
然后,在这棵重构树上进行dp,核心内容一致,区别在于原边权变为了点权,原子树大小变为叶子节点个数。
时间复杂度
空间复杂度
代码
方法一
#include <bits/stdc++.h> using namespace std; using ll = long long; struct DSU { vector<int> fa; vector<int> sz; DSU(int n = 0) { init(n); } void init(int n) { fa.assign(n + 1, 0); sz.assign(n + 1, 1); iota(fa.begin(), fa.end(), 0); } int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); } bool same(int x, int y) { return find(x) == find(y); } void merge(int x, int y) { sz[y] += sz[x]; fa[find(x)] = find(y); } }; tuple<int, int, int> e[3007]; bool black[3007]; int cost[3007]; ll f[3007][3007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for (int i = 1;i <= n;i++) cin >> black[i]; for (int i = 1;i <= n;i++) { cin >> cost[i]; f[i][black[i]] = 0; f[i][black[i] ^ 1] = -cost[i]; } for (int i = 1;i <= n - 1;i++) { int u, v, w; cin >> u >> v >> w; e[i] = { w,u,v }; } sort(e + 1, e + n); DSU dsu(n); for (int i = 1;i <= n;i++) { auto [w, u, v] = e[i]; u = dsu.find(u); v = dsu.find(v); vector<ll> g(n + 1, -1e18); for (int j = 0;j <= dsu.sz[u];j++) { for (int k = 0;k <= dsu.sz[v];k++) { ll val = 1LL * (j * (dsu.sz[v] - k) + k * (dsu.sz[u] - j)) * w; g[j + k] = max(g[j + k], f[u][j] + f[v][k] + val); } } for (int j = 0;j <= dsu.sz[u] + dsu.sz[v];j++) f[u][j] = g[j]; dsu.merge(v, u); } ll ans = 0; for (int i = 0;i <= n;i++) ans = max(ans, f[dsu.find(1)][i]); cout << ans << '\n'; return 0; }
方法二
#include <bits/stdc++.h> using namespace std; using ll = long long; struct DSU { vector<int> fa; vector<int> sz; DSU(int n = 0) { init(n); } void init(int n) { fa.assign(n + 1, 0); sz.assign(n + 1, 1); iota(fa.begin(), fa.end(), 0); } int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); } bool same(int x, int y) { return find(x) == find(y); } void merge(int x, int y) { sz[y] += sz[x]; fa[find(x)] = find(y); } }; int n; struct edge { int u, v, w; }e[3007]; bool black[3007]; int cost[3007]; int g_w[6007]; vector<int> g[6007]; DSU dsu; void kruskal_rebuild() { sort(e + 1, e + n, [&](const edge &a, const edge &b) {return a.w < b.w;}); dsu.init(2 * n - 1); for (int i = 1;i <= n - 1;i++) { auto [u, v, w] = e[i]; u = dsu.find(u); v = dsu.find(v); g_w[n + i] = w; g[n + i].push_back(u); g[n + i].push_back(v); dsu.merge(u, n + i); dsu.merge(v, n + i); } } /// kruskal重构树,O(mlogm),图重构为树后任意两点LCA的权值是路径瓶颈 //* 最小生成树 <=> u-v所有路径最大边权中的最小值 ll sz[6007], f[6007][3007]; void dfs(int u) { sz[u] = u <= n; for (auto v : g[u]) { dfs(v); vector<ll> ff(n + 1, -1e18); for (int i = 0;i <= sz[u];i++) { for (int j = 0;j <= sz[v];j++) { ll val = 1LL * (i * (sz[v] - j) + j * (sz[u] - i)) * g_w[u]; ff[i + j] = max(ff[i + j], f[u][i] + f[v][j] + val); } } for (int i = 0;i <= sz[u] + sz[v];i++) f[u][i] = ff[i]; sz[u] += sz[v]; } } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n; for (int i = 1;i <= n;i++) cin >> black[i]; for (int i = 1;i <= n;i++) { cin >> cost[i]; f[i][black[i]] = 0; f[i][black[i] ^ 1] = -cost[i]; } for (int i = 1;i <= n - 1;i++) { int u, v, w; cin >> u >> v >> w; e[i] = { u,v,w }; } kruskal_rebuild(); dfs(2 * n - 1); ll ans = 0; for (int i = 0;i <= n;i++) ans = max(ans, f[2 * n - 1][i]); cout << ans << '\n'; return 0; }
B
题解
知识点:排列组合。
只有大小相同的多重子集才能产生贡献。对于一对子集,显然从小到大排序后,对应数字的差的绝对值的和就是最小操作次数,现在考虑枚举每个点对产生的贡献。
对于一组点对 表示A中第 个数和B中第 个数在对应位置,那么包含它们的子集有:
直接算是 的,无法预处理,这里需要用到范德蒙德卷积公式:
其中一个推论是:
因此原式可以化简为 的计算。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; const int P = 998244353; namespace Number_Theory { const int N = 4e3 + 7; int qpow(int a, ll k) { int ans = 1; while (k) { if (k & 1) ans = 1LL * ans * a % P; k >>= 1; a = 1LL * a * a % P; } return ans; } int fact[N], invfact[N]; void init(int n) { fact[0] = 1; for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P; invfact[n] = qpow(fact[n], P - 2); for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P; } } namespace CNM { using namespace Number_Theory; int C(int n, int m) { if (n == m && m == -1) return 1; //* 隔板法特判 if (n < m || m < 0) return 0; return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P; } } /// 公式法求组合数,O(n),预处理阶乘及其逆元快速求出组合数 int a[2007], b[2007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for (int i = 1;i <= n;i++) cin >> a[i]; for (int i = 1;i <= n;i++) cin >> b[i]; sort(a + 1, a + n + 1); sort(b + 1, b + n + 1); CNM::init(2 * n); int ans = 0; for (int i = 1;i <= n;i++) { for (int j = 1;j <= n;j++) { int val = abs(a[i] - b[j]); (ans += 1LL * CNM::C(i + j - 2, min(i, j) - 1) * CNM::C(2 * n - i - j, min(n - i, n - j)) % P * val % P) %= P; } } cout << ans << '\n'; return 0; }
C
题解
知识点:数学。
显然, 的个数远比 多,因此我们只需要计算 因子的个数即可。
我们化简后有如下式子:
注意到, 的倍数会贡献一次, 的倍数又会贡献一次,以此类推。因此,我们按 的幂求幂次数总和即可。
但是,这里奇数和偶数的幂次规律是不同的,但都是等差,分别求一下即可。例如, 的倍数时,分别求 和 两个幂次等差数列的和即可。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; using i128 = __int128_t; template<class T> inline void write(T x) { if (x < 0) { putchar('-');x = -x; } if (x >= 10) write(x / 10); putchar(x % 10 + '0'); } i128 calc1(i128 n, i128 x) { i128 a1 = (n + 1) / 2 - x / 2; i128 an = a1 % x; i128 cnt = (a1 - an) / x + 1; return (a1 + an) * cnt / 2; } i128 calc0(i128 n, i128 x) { i128 a1 = n / 2 - x + 1; i128 an = a1 % x; i128 cnt = (a1 - an) / x + 1; return (a1 + an) * cnt / 2; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); ll n; cin >> n; i128 x = 5, ans = 0; while (n >= x) { if (n >= 2 * x) ans += calc0(n, x); ans += calc1(n, x); x *= 5; } write(ans); puts(""); return 0; }
E
题解
知识点:枚举,前缀和。
我们求出前缀和 ,那么原式改写为 。显然,当 和 不同奇偶时无解,否则需要在 的前缀和之间找到 个和 同奇偶的位置,这个过程可以用 记录前缀和奇偶个数的前缀和,可以 查询。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; ll a[100007]; int cnt[100007]; bool solve() { int n, q; cin >> n >> q; for (int i = 1;i <= n;i++) { cin >> a[i]; a[i] += a[i - 1]; cnt[i] = (a[i] & 1) + cnt[i - 1]; } while (q--) { int l, r, k; cin >> l >> r >> k; if ((a[l - 1] & 1) != (a[r] & 1)) { cout << "NO" << '\n'; continue; } int res = cnt[r - 1] - cnt[l - 1]; if (!(a[l - 1] & 1)) res = r - l - res; if (res >= k - 1) cout << "YES" << '\n'; else cout << "NO" << '\n'; } return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << -1 << '\n'; } return 0; }
G
题解
知识点:GCD和LCM。
可以分三类讨论:
- 当 时,当且仅当 时有解。
- 否则,当 或 时,当且仅当 为其中非零数的倍数时有解。
- 否则,当且仅当 是 或者 的倍数时有解。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; bool solve() { int x, y, z; cin >> x >> y >> z; int d = gcd(x, y); if (x == 0 && y == 0) { if (z == 0) cout << "YES" << '\n'; else cout << "NO" << '\n'; } else if (x == 0 || y == 0) { if (z % d == 0) cout << "YES" << '\n'; else cout << "NO" << '\n'; } else { if (z && z % d == 0) cout << "YES" << '\n'; else cout << "NO" << '\n'; } return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << -1 << '\n'; } return 0; }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/17619254.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2022-08-10 NC207781 迁徙过程中的河流
2022-08-10 NC14526 购物
2022-08-10 NC23413 小A买彩票
2022-08-10 NC16619 [NOIP2008]传球游戏
2022-08-10 NC16856 [NOI1999]钉子和小球.md
2022-08-10 NC16850 [NOI1998]免费馅饼
2022-08-10 NC51216 花店橱窗