Atcoder Beginner Contest 267
C. Index × A(Continuous ver.)
题目大意
给定一个序列,找一长度为 的连续子序列,其 值最大,求该最大值。
解题思路
枚举该子序列即可,注意到下一个子序列的值可以由上一个子序列的值转移过来,它们就相差了一个区间和以及
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<LL> a(n + 1, 0); LL ans = -1e18; LL sum = 0; LL tmp = 0; for(int i = 1; i <= n; ++ i){ cin >> a[i]; if (i <= m){ sum += a[i]; tmp += a[i] * i; }else{ ans = max(ans, tmp); tmp -= sum; sum -= a[i - m]; sum += a[i]; tmp += a[i] * m; } } ans = max(ans, tmp); cout << ans << endl; return 0; }
D. Index × A(Not Continuous ver.)
题目大意
给定一个序列,找一长度为 的(不连续)子序列,其 值最大,求该最大值。
解题思路
设表示前 个数选了 个数的最大值,对于第个数考虑选或不选转移即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<vector<LL>> dp(n + 1, vector<LL>(m + 1, -1e18)); dp[0][0] = 0; for(int i = 1; i <= n; ++ i){ LL a; cin >> a; dp[i][0] = 0; for(int j = 1, up = min(i, m); j <= up; ++ j){ dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + a * j); } } cout << dp[n][m] << '\n'; return 0; }
E. Erasing Vertices 2
题目大意
给定一张个点 条边的无向图,点有点权。需要进行次操作,每次操作,选择一个点,并移除该点以及与该点相连的所有边,其代价是与点直接相连的所有点权和。问所有操作的代价的最大值的最小值是多少。
解题思路
我们每次移除当前代价最小的那个,那么最终一定是最大值最小的情况。于是我们用优先队列维护这个代价最小的那个点,移除后暴力修改周围点的代价,并把新的代价加入到优先队列里。
优先队列里出队时先看其代价是不是当前点的代价,就能判断这个是不是最新的代价了。
暴力修改的复杂度其实就是边的数量,因此总的时间复杂度是
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<LL> a(n); for(auto &i : a) cin >> i; vector<vector<int>> edge(n, vector<int>()); vector<LL> sum(n, 0); for(int i = 1; i <= m; ++ i){ int x, y; cin >> x >> y; -- x; -- y; edge[x].push_back(y); edge[y].push_back(x); sum[x] += a[y]; sum[y] += a[x]; } priority_queue<pair<LL, int>> qwq; for(int i = 0; i < n; ++ i){ qwq.push({- sum[i], i}); } LL ans = 0; int cnt = n; set<int> ff; while(cnt){ auto [val, u] = qwq.top(); val = -val; qwq.pop(); if (val != sum[u]) continue; ff.insert(u); ans = max(ans, val); cnt --; for(auto &v : edge[u]){ if (ff.find(v) != ff.end()) continue; sum[v] -= a[u]; qwq.push({-sum[v], v}); } } cout << ans << '\n'; return 0; }
F. Exactly K Steps
题目大意
给定一棵树,有个询问,第 个询问求任意一个与点 距离的点的下标。
解题思路
考虑到树上往父亲跑容易,但往儿子跑不太容易,因此我们都尽量往父亲跑。
注意到,任意一个点与树上的点距离最远的,一定是这棵树的直径上的点,这个从直径的证明里可以得出。
因此先跑两次求得树的直径,然后再分别以直径两点为根,预处理往上跳的倍增数组。对于询问中的点,就在距离根节点最远的那棵树往上跳步就能得到答案。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; #define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i) #define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i) const int N = 2e5 + 8; const int SP = 25; vector<int> G[N]; int n, q; struct tu{ int up[N][SP]; int dep[N]; void dfs(int u, int fa) { up[u][0] = fa; dep[u] = dep[fa] + 1; FOR (i, 1, SP) up[u][i] = up[up[u][i - 1]][i - 1]; for (int& v: G[u]) { if (v == fa) continue; dfs(v, u); } } int lca(int u, int t) { FOR (i, 0, SP) if (t & (1 << i)) u = up[u][i]; return u; } }l1, l2; int dep[N]; int solve(int u, int fa) { dep[u] = dep[fa] + 1; int maxDeep = u; for (int& v: G[u]) { if (v == fa) continue; int maxd = solve(v, u); if (dep[maxDeep] < dep[maxd]) maxDeep = maxd; } return maxDeep; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n; for(int i = 1; i < n; ++ i){ int u, v; cin >> u >> v; G[u].push_back(v); G[v].push_back(u); } int l = solve(1, 1); dep[l] = 0; int r = solve(l, l); l1.dfs(l, l); l2.dfs(r, r); cin >> q; tu* L; for(int i = 1; i <= q; ++ i){ int u, k; cin >> u >> k; if (l1.dep[u] < l2.dep[u]){ L = &l2; }else { L = &l1; } if (L->dep[u] <= k) cout << -1 << '\n'; else cout << L->lca(u, k) << '\n'; } return 0; }
G. Increasing K Times
题目大意
给定一个序列,可打乱其排列顺序,问有多少个排列情况满足,恰好有个数对,前者小于后者。
解题思路
神奇的代码
Ex. Odd Sum
题目大意
给定一个序列,其中 ,问有多少个长度为奇数的子序列其和为
解题思路
朴素的很容易可以想到设 表示前 个数,选了偶数/奇数个,和为 的方案数。虽然第一维可以再压缩一下,将 这 个数依次考虑,考虑选多少个数出来,最后再 一下,但因为 状态里面,的状态始终降不下来。
这里考虑下转移策略,我们的转移方程始终是从转移过来的,即 的状态和 的状态结合,得到了的状态。每次转移长度都增加 。
那么考虑倍增地方式结合。一开始我们有 个状态,分别是 。然后像线段树合并或者分治合并的方式,先让 合并成 , 合并成 ,然后 合并成 这样,这样我们每次合并时的长度都会翻倍,因此我们只需要合并 次就可以得到的状态,也即 。
而对于合并,观察转移方程 ,第三维是个卷积形式,因此我们将和 分别看成一个多项式,其中 的系数为 和 ,转移的时候就是两个多项式相乘,用 加速计算。
合并两个多项式也即:
理论时间复杂度是,但由于中间实现会产生较多次的临时数组创建开销,不知该怎么优雅的实现榜一3分钟A的代码还确实用这种做法能过,但其贴了多项式全家桶长达800行不知怎么做到的
有个一个小小的优化,就是由于只有个数,我们就预处理每一个数的选择方案,也即 表示一个由选择了偶数个/奇数个的多项式, 其中的系数表示和为 的方案数,其实就是个组合数,其中表示序列中 个个数。然后我们只需要做 次 就可以了。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; #define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i) #define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i) const int N = 1e5 + 8; const LL MOD = 998244353; const int G = 3; int n, m; int cnt[11]; vector<LL> dp[11][2]; vector<LL> f[2], g[2]; LL bin(LL x, LL n, LL MOD) { LL ret = MOD != 1; for (x %= MOD; n; n >>= 1, x = x * x % MOD) if (n & 1) ret = ret * x % MOD; return ret; } inline LL get_inv(LL x, LL p) { return bin(x, p - 2, p); } LL wn[(N * 10) << 2], rev[(N * 10) << 2]; int NTT_init(int n_) { int step = 0; int n = 1; for ( ; n < n_; n <<= 1) ++step; FOR (i, 1, n) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (step - 1)); int g = bin(G, (MOD - 1) / n, MOD); wn[0] = 1; for (int i = 1; i <= n; ++i) wn[i] = wn[i - 1] * g % MOD; return n; } void NTT(vector<LL>& a, int n, int f) { FOR (i, 0, n) if (i < rev[i]) std::swap(a[i], a[rev[i]]); for (int k = 1; k < n; k <<= 1) { for (int i = 0; i < n; i += (k << 1)) { int t = n / (k << 1); FOR (j, 0, k) { LL w = f == 1 ? wn[t * j] : wn[n - t * j]; LL x = a[i + j]; LL y = a[i + j + k] * w % MOD; a[i + j] = (x + y) % MOD; a[i + j + k] = (x - y + MOD) % MOD; } } } if (f == -1) { LL ninv = get_inv(n, MOD); FOR (i, 0, n) a[i] = a[i] * ninv % MOD; } } vector<LL> operator+(vector<LL> a, const vector<LL>& b){ a.resize(max(a.size(), b.size())); for(int i = 0; i < b.size(); ++ i) a[i] = (a[i] + b[i]) % MOD; return a; } vector<LL> conv(vector<LL> a, vector<LL> b) { int len = a.size() + b.size() - 1; int n = NTT_init(len); a.resize(n); b.resize(n); NTT(a, n, 1); NTT(b, n, 1); FOR (i, 0, n) a[i] = a[i] * b[i] % MOD; NTT(a, n, -1); a.resize(len); return a; } LL invf[N], fac[N] = {1}; void fac_inv_init(LL n, LL p) { FOR (i, 1, n) fac[i] = i * fac[i - 1] % p; invf[n - 1] = bin(fac[n - 1], p - 2, p); FORD (i, n - 2, -1) invf[i] = invf[i + 1] * (i + 1) % p; } LL C(int n, int m){ if (n < m) return 0; return fac[n] * invf[m] % MOD * invf[n - m] % MOD; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m; fac_inv_init(n + 1, MOD); for(int i = 1; i <= n; ++ i){ int a; cin >> a; cnt[a] ++; } for(int i = 1; i <= 10; ++ i){ dp[i][0].resize(cnt[i] * i + 1); dp[i][1].resize(cnt[i] * i + 1); for(int j = 0; j <= cnt[i]; ++ j){ dp[i][j & 1][i * j] = C(cnt[i], j); } } f[0].resize(1); f[0][0] = 1; for(int i = 1; i <= 10; ++ i){ g[0] = conv(f[0], dp[i][0]) + conv(f[1], dp[i][1]); g[1] = conv(f[1], dp[i][0]) + conv(f[0], dp[i][1]); f[0].swap(g[0]); f[1].swap(g[1]); } if (f[1].size() <= m) cout << 0 << '\n'; else cout << f[1][m] << '\n'; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库