340
A - Arithmetic Progression
给出等差数列的
void solve() {
int a, b, d;
cin >> a >> b >> d;
for (int i = a;i <= b;i += d) {
cout << i << " ";
}
}
B - Append
你有一个空序列
1 x
:将追加到 的末尾。 2 k
: 从的末尾找到 的值。当给出这个查询时,可以保证 的长度至少是 。
void solve() {
int q;cin >> q;
vector<int> a;
while (q--) {
int op;cin >> op;
if (op == 1) {
int x;cin >> x;
a.push_back(x);
} else {
int k;cin >> k;
cout << a[a.size() - k] << '\n';
}
}
}
C - Divide and Divide
给出一个
重复下面的一系列操作,直到所有不小于
可以证明,无论操作的顺序如何,他支付的总金额是不变的。
- 选择写在黑板上的一个不小于
的整数 。 - 擦去黑板上出现的一个
。然后,在黑板上写下两个新的整数 和 。 - 高桥必须支付
日元才能完成这一系列操作。
当不能再进行操作时(即全部都是 1),高桥支付的总金额是多少?
Solution
坑点:
ceil (n*1.0/2)
有精度误差最好换成(n+1)/2
- 数据较大时,不要使用
pow()
函数,有精度丢失,肯定会 wa,更推荐快速幂
本来我的思路是写一个递推: f[n]=f[n/2]+f[(n+1)/2]+n
(但是由于
注意到一个有趣的规律(是可行的):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ||
---|---|---|---|---|---|---|---|---|---|---|---|
ans | 0 | 2 | 5 | 8 | 12 | 16 | 20 | 24 | 29 | 34 | |
dif|ans | 0 | 2 | 3 | 3 | 4 | 4 | 4 | 4 | 5 | 5 | |
dif|dif|ans) | 0 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | |
从 3 开始 `dif | ans` 每隔 |
设
void solve() {
ll n, k = -1;cin >> n;
for (ll i = 1;i < n;i += qpow(2, k))k++;
ll ans = 0;
for (ll i = 0;i < k;i++) {
ans += (i + 2) * qpow(2, i);
}
ans += (n - qpow(2, k)) * (k + 2);
cout << ans << '\n';
}
递归的话,需要存储一下 (记忆化搜索)
(PS: 学会 lambda
表达式的递归调用(可以看 Jiangly ))
#define int long long
unordered_map<int, int> memo;
int f(int n) {
if (n == 1) return 0;
if (memo.count(n)) return memo[n];
return memo[n] = f(n / 2) + f(((n + 1) / 2)) + n;
}
unordered_map
也可以换为 map
D - Super Takahashi Bros.
游戏由编号为
对于每个可以下棋的阶段
- 花费
秒清除阶段 。进入 阶段。 - 花费
秒清除阶段 。进入 阶段。
忽略通关时间以外的其他时间,至少需要多少秒才能进入关卡
Solution
最短路
求从 1-n 的最短路。用的邻接表存图
#define int long long
#define pll pair<int,int>
void solve() {
int n;cin >> n;
vector<pll> e[n + 1];
vector<ll> dis(n + 1, INTMAX_MAX), vis(n + 1, 0);
for (int i = 1;i < n;i++) {
int a, b, x;cin >> a >> b >> x;
e[i].push_back({i + 1,a});
e[i].push_back({x,b});
}
priority_queue<pll>q;
q.push({0,1});
dis[1] = 0;
while (q.size()) {
auto u = q.top().second;q.pop();
if (vis[u])continue;
vis[u] = 1;
for (auto [v, w] : e[u]) {
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.push({-dis[v],v});
}
}
}
cout << dis[n] << ' ';
}
E - Mancala 2
有
高桥将依次对
- 将变量
设为 。 - 从盒子
中取出所有的球并握在手中。 - 在手拿至少一个球的同时,重复下面的过程:
- 将
的值增加 。 - 将手中的一个球放入盒子
。
- 将
完成所有操作后,确定每个盒子中的球数。
Solution
线段树(待更
我本来是想用差分来做:先记录本来
区间修改一般还是线段树
官方题解:
考虑将 N 个球放入箱子的操作,该过程可以重新表述如下:
- 从箱子
中取出所有的球并拿在手中。 - 让
表示手中的球的数量。将 个球放入每个箱子中。 - 让
表示手中剩余的球的数量。将一个球放入箱子 到箱子 中。
(如果,“箱子 到箱子 ”表示箱子 到箱子 ,以及箱子 到箱子 。)
必须对表示每个箱子中球数量的数组进行点更新和段增加等操作。使用类似懒惰段树的数据结构,它们可以在
待我学习线段树
F - S = 1
给你整数
请找出一对满足以下所有条件的整数 -1
。
- 顶点位于
平面上点 的三角形的面积为 .
Solution
易得:
则到
则三角形的面积:
设
扩展欧几里得算法,给定一个整数对
作为输入,找到一个整数对 ,使得 ,时间复杂度为 。(这里,整数对 保证是满足 的整数。)
通过将
将
pair<ll, ll> extgcd(ll a, ll b) {
if (!b) return {1, 0};
ll x, y;
tie(y, x) = extgcd(b, a % b);
y -= a / b * x;
return {x, y};
}
void solve() {
ll x, y;cin >> x >> y;
if (abs(__gcd(x, y)) > 2) {
cout << "-1\n";return;
}
auto [c, d] = extgcd(y, -x);
c *= 2 / abs(__gcd(x, y)), d *= 2 / abs(__gcd(x, y));
cout << c << " " << d << '\n';
}
G - Leaf Color
有一棵树
求满足以下条件的顶点集合
的子图 由 引导,满足以下所有条件: 是一棵树。 - 所有阶数为
的顶点颜色相同。
什么是诱导子图?假设
Solution
虚数(辅助树)+DP
(待更
官方题解:
这个问题有几种解决方案;在这篇社论中,我们介绍一种叫做辅助树的技术。
本社论的方法
我们首先解释解决这个问题的方法。
固定度为 1 的顶点的颜色
固定颜色
考虑一下在压缩时我们需要保留哪些顶点。除了颜色为
(图示:当顶点
我们把通过压缩树保持指定顶点的 LCA 关系而得到的树称为通过压缩树获得的辅助树。(这在日本和中国等地通常称为辅助树、虚拟树或压缩树。在这篇社论中,我们简单地称之为辅助树。)
- 关于这个树的名字,也可以参考以下文章。Auxiliary Tree に関する Kyopro Encyclopedia of Algorithms の記事(“竞赛编程”算法百科全书中关于辅助树的日文文章)
辅助树的性质
辅助树的正式定义如下。给定一个有根树
并且在
我们介绍辅助树的一个性质。
性质 1
。
(证明)对树
设
引理 1
对于大于或等于 3 的整数
,如果 ,那么 。
(引理证明)我们用反证法证明。假设存在
正如引理所示,
这个方程意味着
通过对顶点集大小进行归纳,可以证明
此性质意味着辅助树有
辅助树的构造
给定一个顶点集
我们解释一下构造的过程。我们利用以下性质:
性质 2 对树
的 DFS 进行任意一种先序遍历。设 是按先序排列的 中的顶点。
那么有以下属性:
(证明)
利用上面性质 1 的证明中展示的方程
)}
,得出
这就是我们想要的。(证明结束)
利用性质 2,可以按照如下方式构造
- 预先计算加倍或 HLD(重链剖分),以便快速得到
上任意两个顶点的 LCA。 - 取
中顶点的先序遍历 。 - 得到
,和 。 - 对
按照先序排列排序并去除重复项。
一旦获得发生的顶点,剩下的就是在维护一个栈的同时扫描这个序列。(我们不详细解释。有关实际过程,请参见 yaketake08的文章(日文),专栏“ソート2 回の方法”。(译者注:也可以参考 yaketake08的文章(日文),专栏“ソート2 回の方法”。))
基于到目前为止介绍的技术,可以从每种颜色的顶点集生成辅助树并执行树 DP,从而以总共