20230414 训练记录:前后缀
Posted Patricky
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20230414 训练记录:前后缀相关的知识,希望对你有一定的参考价值。
时间过得真快啊
去年这个时候打 ZUCC 校赛同步赛时还在牛客写了两个题解,里面有句话:
一年后的我已经学会了 lca,那现在算不算长大了呢?
这两篇题解也还挺有意思的,搬到这里好了!
Sum of Numerators
给定 \\(N, K\\),求解将序列 \\(\\left\\\\dfraci2^K\\right\\\\) 中元素全部约分后的分子和,其中 \\(i\\) 遍历 \\(1 \\sim N\\)。
\\(N \\in [1, 10^9], K \\in [0, 10 ^ 9]\\)
首先注意到 \\(\\gcd(2^K, \\mathrmodd) = 1\\),于是我们先将所有奇数和算入答案,我们知道:
接下来考虑偶数与 \\(2 ^ K\\) 约分的过程,偶数可写作 \\(X = x2 ^ t,\\;\\mathttwhere\\;\\gcd(2, x) = 1\\)。于是 \\(2 ^ K\\) 将会约分所有 \\(t \\le K\\) 的部分,使得其变为一段奇数和,使用上述算式求解即可。
最后再加上没有被约分的偶数和即可,即
展开代码
using i64 = long long;
void solve()
int n, k;
std::cin >> n >> k;
i64 ans = 0;
for (k += 1; k -- && n; n -= (n + 1) / 2)
ans += 1LL * ((n + 1) / 2) * ((n + 1) / 2);
(!k) && (ans += 1LL * (n / 2) * (n / 2 + 1));
std::cout << ans << \'\\n\';
Disjoint Path On Tree
给定一棵 \\(N\\) 个节点的树,求解二元组 \\((u, v)\\) 的个数,其中 \\(u, v\\) 是俩不相交的简单路径。
路径 \\((i, j)\\) 与 \\((j, i), \\,i \\ne j\\) 视作同一路径。\\(N \\in [1, 2 \\times 10 ^ 5]\\)
显然,树上的路径均为简单路径。容易知道 \\(N\\) 个节点的树的简单路径条数为:
而 \\((i, j)\\) 与 \\((j, i), \\,i \\ne j\\) 视作一样的也没关系,求出所有二元组后除以二即可。
另一方面,所求也等价于 \\(\\Big((F(N) ^ 2 -\\) 相交组数 \\(\\Big)\\),设两条路径交于 \\(u\\),即选择
讨论 \\(x, y\\) 的来源:
- \\(x \\in u\\),即 \\(\\mathrmcnt_1 = F(u) - \\displaystyle \\sum_to \\;\\in\\;u F(to)\\)。
- \\(x \\notin u\\),即 \\(\\mathrmcnt_2 = \\mathrmsize_u \\times (n - \\mathrmsize_u)\\)。
注意不能是两点均来自 \\(x \\notin u\\)。组合起来:
展开代码
Z f(int n) return 1LL * n * (n + 1) / 2;
void solve()
int n;
std::cin >> n;
std::vector<std::vector<int>> G(n);
for (int i = 1, u, v; i < n; ++i)
std::cin >> u >> v;
-- u, -- v;
G[u].push_back(v);
G[v].push_back(u);
Z ans = f(n) * f(n);
std::vector<int> size(n);
std::function<void(int, int)> dfs = [&](int u, int p)
size[u] = 1;
for (auto &&to : G[u]) if (to != p)
dfs(to, u);
size[u] += size[to];
Z cnt1 = f(size[u]);
Z cnt2 = 1LL * size[u] * (n - size[u]);
for (auto &&to : G[u]) if (to != p)
cnt1 -= f(size[to]);
ans -= (cnt1 + cnt2) * (cnt1 + cnt2) - cnt2 * cnt2;
;
dfs(0, -1);
std::cout << ans / 2 << \'\\n\';
至多删除一/两段的最大子段和
集训队小伙伴问的面试题,挺有意思的。中途还断断续续地去请教了 tarjen 大佬,感激不尽。
至多一段
注意到,最终的答案是两段拼起来的:枚举 \\(i\\),求出 \\(i\\) 左侧的最大子段和、右侧的最大子段和,作为候选答案。实际上问题转换为求出前缀、后缀的最大子段和的最大值。这种前后缀拼起来贡献答案的题挺常见的。
展开代码
#include <bits/stdc++.h>
int main()
std::cin.tie(nullptr)->sync_with_stdio(false);
int n;
std::cin >> n;
std::vector<int> a(n + 1), fp(n + 1), fs(n + 2);
for (int i = 1; i <= n; i++) std::cin >> a[i];
for (int i = 1; i <= n; i++) fp[i] = std::max(fp[i - 1], 0) + a[i];
for (int i = 1; i <= n; i++) fp[i] = std::max(fp[i], fp[i - 1]);
for (int i = n; i >= 1; i--) fs[i] = std::max(fs[i + 1], 0) + a[i];
for (int i = n; i >= 1; i--) fs[i] = std::max(fs[i], fs[i + 1]);
int ans = 0;
for (int i = 1; i <= n; i++)
ans = std::max(ans, fp[i - 1] + fs[i + 1]);
std::cout << std::max(ans, fp[n]) << \'\\n\';
return 0;
至多两段
我首先想的是,是否等价于做两次上面的那个问题。然后被群友 Hack 了:
这时候 tarjen 好哥哥说直接 \\(\\mathcal O(n \\log n)\\) 扫一遍就好了,我却没有理解到,接着加好友学了一波。答案实际上也是两段,不过这次是前区间和最小的两段,问题转换为求出所有前缀中,区间和最小的一段和为多少。区间和是两段前缀和之差。即对于当前遍历到的 \\(i\\),前缀和为 \\(s_i\\),找出所有前缀和 \\(s_j\\,(j \\lt i)\\) 中的最大值,和 \\(s_i\\) 的差就是前缀中最小的那段区间和了。
举一些例子来描述这个过程:
-1 -1 -1 -1 -1 [-1]
当前遍历到的前缀和为 \\(-6\\),前缀和集合为 \\(\\colorred0\\) \\(-1, -2, -3, -4, -5\\) 最大的是 \\(0\\),因此当前最小区间和为 \\(-6\\)。
1 2 3 -6 5 [-6]
当前遍历到的前缀和为 \\(-4\\),前缀和集合为 \\(0, 1, 3, 5, 6\\),所以求出最小的区间和为 \\(-1 - 6 = -7\\)。
展开代码
#include <bits/stdc++.h>
using ll = long long;
int main()
std::cin.tie(nullptr)->sync_with_stdio(false);
int n;
std::cin >> n;
std::vector<int> a(n + 1);
std::vector<ll> ps(n + 1), ss(n + 2);
for (int i = 1; i <= n; i++) std::cin >> a[i];
for (int i = 1; i <= n; i++) ps[i] = ps[i - 1] + a[i];
for (int i = n; i >= 1; i--) ss[i] = ss[i + 1] + a[i];
std::set<ll, std::greater<ll>> s;
constexpr ll inf = 1e18;
std::vector<ll> fp(n + 1), fs(n + 2);
fp[0] = fs[n + 1] = inf;
for (int i = 1; i <= n; i++)
s.insert(ps[i - 1]);
fp[i] = std::min(fp[i - 1], ps[i] - *s.begin());
s.clear();
for (int i = n; i >= 1; i--)
s.insert(ss[i + 1]);
fs[i] = std::min(fs[i - 1], ss[i] - *s.begin());
fp[0] = fs[n + 1] = 0;
ll ans = 0;
for (int i = 1; i <= n; i++)
ans = std::min(ans, fp[i - 1] + fs[i + 1]);
std::cout << std::max<ll>(0, ps[n] - ans) << \'\\n\';
return 0;
Dailight 训练记录
现场赛记录
19 CCPC 湘潭邀请赛 11/Gold
19 ICPC 西安邀请赛 49/Silver
训练规划:
hl:
1.深入增强图论,数据结构的能力,包括但不限于树形dp,点分治,多写dp,学习斜率优化,四边形优化
2.保证银牌及以下图论,数据结构,dp的通过率,最好可以在十分钟内出思路
3.养成提交前检查代码的习惯,不出现傻逼错误。
4.学习简单数论(逆元,组合数),尤其是数论结合图论的应用
gbs:
1.加快上机 -> 写完代码过样例 这一过程的速度。
2.减少debug占用机时
3.首要任务是增加银牌及以下数论的通过率,包括但不限于打表找规律,基础推公式,其次学习较难的数论。
4.增加用简要语言表述题意的能力
zcz.
1.训练计算几何的能力,保证简单及中等计算几何的通过率
2.多看一些思维题及找规律,推公式,构造题,写代码为次,提出正确思路为主。
3.写题的过程尽量了解ACM的出题套路。
总:
1.所有队员多做思维题,保证脑子灵活度
2.多开训练赛,增加讨论题目的配合能力以及队伍机时调度的能力
经验总结:
1.交之前务必重新审视两边代码保证正确,WA之后直接下机并且打印代码,2WA之后需要队友确认题意并且检查代码
2.签到题签完之后如果过的人数中等且写题的十分自信,可以本题单线程,否则需要其他至少一位队友确认题意并且确认写法才可敲题
3.开局开荒题的队员除非特别有自信且剩余码量不高,否则优先将机子让给队友写签到题以降低罚时
4.非最后十分钟垃圾时间严禁挂机,当前没有题挂在线程上的队员就去读没有读过的题
5.难题采用数论给gbs和zcz讨论,图论数据结构给hl,计算几何给zcz,无法确定类型的读给hl确认类型的战略,确认题是否可做
训练赛记录
The 10th Shandong Provincial Collegiate Programming Contest 11/13 rak3/249
以上是关于20230414 训练记录:前后缀的主要内容,如果未能解决你的问题,请参考以下文章