AtCoder Beginner Contest 340
AtCoder Beginner Contest 340
![image-20240210224000170](AtCoder Beginner Contest 340.assets/image-20240210224000170.png)
稳定发挥,速度有点慢。
C - Divide and Divide
Problem Statement
黑板上写着一个整数 \(N\)。
高桥将重复下面的一系列操作,直到所有不小于 \(2\) 的整数都从黑板上移除:
- 选择写在黑板上的一个不小于 \(2\) 的整数 \(x\)。
- 擦去黑板上出现的一个 \(x\)。然后,在黑板上写下两个新的整数 \(\left \lfloor \dfrac{x}{2} \right\rfloor\) 和 \(\left\lceil \dfrac{x}{2} \right\rceil\)。
- 高桥必须支付 \(x\) 日元才能完成这一系列操作。
这里,\(\lfloor a \rfloor\) 表示不大于 \(a\) 的最大整数,\(\lceil a \rceil\) 表示不小于 \(a\) 的最小整数。
当不能再进行操作时,高桥支付的总金额是多少?
可以证明,无论操作的顺序如何,他支付的总金额是不变的。
Solution
设 \(f(n)\) 表示如果最开始黑板上写着 \(n\),总金额是多少。
首先高桥会擦掉 \(n\),花费 \(n\) 的代价。
然后写上 \(\left \lfloor \dfrac{x}{2} \right\rfloor\) 和 \(\left\lceil \dfrac{x}{2} \right\rceil\),分别花费 \(f(\left \lfloor \dfrac{x}{2} \right\rfloor)\) 和 \(f(\left\lceil \dfrac{x}{2} \right\rceil)\) 的代价。
所以有转移 \(f(n) = n + f(\left \lfloor \dfrac{x}{2} \right\rfloor)+f(\left\lceil \dfrac{x}{2} \right\rceil)\)。记忆化搜索之。
Code
map<int, int> p;
int f(int n) {
if (n < 2) return 0;
if (p.count(n)) return p[n];
return p[n] = n + f(n / 2) + f((n + 1) / 2);
}
signed main()
{
int n = read();
wel(f(n));
return 0;
}
D - Super Takahashi Bros.
Problem Statement
高桥正在玩一个游戏。
游戏由编号为 \(1,2,\ldots,N\) 的 \(N\) 个阶段组成。最初,只有阶段 \(1\) 可以玩。
对于每个可以下棋的阶段 \(i\) ( \(1\leq i \leq N-1\) ),你都可以在阶段 \(i\) 执行以下两个操作中的一个:
- 花费 \(A_i\) 秒清除阶段 \(i\)。这样就可以进入 \(i+1\) 阶段。
- 花费 \(B_i\) 秒清除阶段 \(i\)。这样就可以进入 \(X_i\) 阶段。
忽略通关时间以外的其他时间,至少需要多少秒才能通关 \(N\)?
Solution
DP 然后记忆化搜索是不对的,因为转移成环。
直接联想图论。我们建一张有向带权图,对于其中的一条 \(u \to v\) 边权为 \(w\) 的边而言,它的含义为在花费 \(w\) 秒打完关卡 \(u\) 后,可以直接来到关卡 \(v\)。
所以我们连边 \(i \to i + 1\) 边权为 \(A_i\),\(i \to X_i\) 边权为 \(B_i\)。
那么根据我们建图中的定义,显然图上 \(1\) 到 \(n\) 的最短路径长度即为答案。
使用堆优化 Dijkstra 即可。
Code
int n, a[N], b[N], x[N], w[N];
int h[N], e[N], ne[N], idx;
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++ ;
}
int dis[N];
bool st[N];
void dij() {
priority_queue<PII, vector<PII>, greater<PII> > q;
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
q.push({0, 1});
while (q.size()) {
int t = q.top().se;
q.pop();
if (st[t]) continue;
st[t] = true;
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (dis[j] > dis[t] + w[i]) {
dis[j] = dis[t] + w[i];
q.push({dis[j], j});
}
}
}
}
signed main()
{
memset(h, -1, sizeof h);
n = read();
fup (i, 1, n - 1) {
a[i] = read(), b[i] = read(), x[i] = read();
add(i, i + 1, a[i]);
add(i, x[i], b[i]);
}
dij();
wel(dis[n]);
return 0;
}
E - Mancala 2
Problem Statement
有 \(N\) 个编号为 \(0\) 至 \(N-1\) 的盒子。最初,\(i\) 盒里有 \(A_i\) 个球。
高桥将依次对 \(i=1,2,\ldots,M\) 进行以下操作:
- 将变量 \(C\) 设为 \(0\)。
- 从盒子 \(B_i\) 中取出所有的球并握在手中。
- 在手拿至少一个球的同时,重复下面的过程:
- 将 \(C\) 的值增加 \(1\)。
- 将手中的一个球放入盒子 \((B_i+C) \bmod N\)。
完成所有操作后,确定每个盒子中的球数。
Solution
可以发现,操作盒子 \(b_i\) 中的球时,将球放入的盒子的下标是连续的。我们令此时 \(b_i\) 这个盒子中的球有 \(x\) 个。
由于 \(x\) 非常大,可能会导致所有盒子中的球数量都增加。不难发现全局会增加 \(\lfloor \frac xn \rfloor\) 次。
剩下的显然有 \(x \bmod n\) 个。此时如果 \(x \bmod n = 0\) 那么直接下一轮即可。以下讨论的均为 \(x \bmod n \ne 0\) 的情况。
我们需要把这些球放入 \(b_i + 1, b_i + 2, \dots, 1, 2, \dots\) 这些盒子中。那么如果 \(b_i + x \le n\),也就是说不会回到前面,那么我们直接将盒子 \(b_i + 1 \sim b_i + x\) 加一即可。
否则 \(b_i + x > n\),首先盒子 \(b_i + 1 \sim n\) 都会加一,然后剩下的 \(x - (n - b_i)\) 会在最前面的几个盒子中依次填充,即盒子 \(1 \sim x - (n - b_i)\) 加一。
所以维护支持区间加,单点查询的线段树然后模拟,就做完了。
Code
int n, m, a[N], b[N];
struct Tree {
int l, r, v, add;
}tr[N << 2];
void pushup(int u) {
tr[u].v = tr[ls].v + tr[rs].v;
}
void calc(int u, int v) {
tr[u].v += v * (tr[u].r - tr[u].l + 1);
tr[u].add += v;
}
void pushdown(int u) {
calc(ls, tr[u].add);
calc(rs, tr[u].add);
tr[u].add = 0;
}
void build(int u, int l, int r) {
tr[u] = {l, r, a[l]};
if (l == r) return;
int mid = l + r >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
pushup(u);
}
void modify(int u, int x) {
if (tr[u].l == tr[u].r) tr[u].v = 0;
else {
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (x <= mid) modify(ls, x);
else modify(rs, x);
pushup(u);
}
}
void modify(int u, int l, int r, int d) {
if (tr[u].l >= l && tr[u].r <= r) calc(u, d);
else {
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (l <= mid) modify(ls, l, r, d);
if (r > mid) modify(rs, l, r, d);
pushup(u);
}
}
int query(int u, int x) {
if (tr[u].l == tr[u].r) return tr[u].v;
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (x <= mid) return query(ls, x);
return query(rs, x);
}
signed main()
{
n = read(), m = read();
fup (i, 1, n) a[i] = read();
fup (i, 1, m) b[i] = read() + 1;
build(1, 1, n);
fup (i, 1, m) {
int x = query(1, b[i]);
modify(1, b[i]);
modify(1, 1, n, (x / n));
x %= n;
if (!x) continue;
if (b[i] + x <= n) modify(1, b[i] + 1, b[i] + x, 1);
else {
if (b[i] == n) modify(1, 1, x, 1);
else {
modify(1, b[i] + 1, n, 1);
modify(1, 1, x - n + b[i], 1);
}
}
}
fup (i, 1, n) wsp(query(1, i));
return 0;
}
F - S = 1
Problem Statement
给你整数 \(X\) 和 \(Y\),它们至少满足 \(X \neq 0\) 和 \(Y \neq 0\) 中的一个。
请找出一对满足以下所有条件的整数 \((A, B)\)。如果不存在这样的一对,请报告。
- \(-10^{18} \leq A, B \leq 10^{18}\)
- 顶点位于 \(xy\) 平面上点 \((0, 0), (X, Y), (A, B)\) 的三角形的面积为 \(1\).
Solution
首先,三个顶点分别在 \((0, 0), (X, Y), (A, B)\) 的三角形面积为 \(\dfrac {|AY-BX|}2\)。在给定 \(X, Y\) 的情况下,我们需要求下列方程的整数解 \(A, B\):
不难发现可以用扩展欧几里得算法来解决。
首先求出最大公约数 \(d = \gcd(X, Y)\)。那么如果 \(d > 2\) 则无解,因为 \(AY - BX\) 一定是 \(d\) 的倍数,而在 \(d>2\) 时 \(d\) 不可能是 \(\pm 2\) 的倍数。
然后我们令 \(X' = Y\),\(Y'= -X\),那么首先我们要求出下列方程的整数解 \(A, B\):
这就是最直接的扩展欧几里得算法所求的式子,可以直接求出 \(A, B\)。
求出来后,我们希望的方程右边应该是 \(\pm 2\) 而不是 \(d\)。所以我们把方程两边同时乘 \(\dfrac 2d\) 或 \(-\dfrac 2d\),即 \(A \gets \dfrac {2A}d, B \gets \dfrac{2B}d\) 或 \(A \gets -\dfrac {2A}d, B \gets -\dfrac{2B}d\) 均可。
Code
int x, y, a, b;
void exgcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1, y = 0;
return;
}
exgcd(b, a % b, y, x);
y -= a / b * x;
}
signed main() {
x = read(), y = read();
int d = __gcd(abs(x), abs(y));
if (d > 2) return puts("-1"), 0;
exgcd(y, -x, a, b);
a *= 2 / d, b *= 2 / d;
wel(a, b);
return 0;
}