AtCoder Beginner Contest 467 ABCDE
A - Obesity
- 预估难度:
入门 - 标签:条件分支
题意
已知 \(\mathrm{BMI}\) 的i计算公式为:
- \(\text{体重}[\mathrm{kg}] \div \text{身高}[\mathrm{m}] \div \text{身高}[\mathrm{m}]\)
请判断身高为 \(H[\mathrm{cm}]\)、体重为 \(W[\mathrm{kg}]\) 的人的 \(\mathrm{BMI}\) 是否达到或超过 \(25 \; \mathrm{kg}/\mathrm{m}^2\)。
数据范围
- \(1 \leq H \leq 300\)
- \(1 \leq W \leq 300\)
思路
模拟计算即可。注意给定的身高单位为厘米,而公式中的身高单位为米。
可以将浮点运算通过转换公式变成整数运算,以保证答案精度。
代码
void solve()
{
int h, w;
cin >> h >> w;
// w / (h / 100) / (h / 100) >= 25
// w >= 25 * (h / 100) * (h / 100)
// w * 10000 >= 25 * h * h
if(w * 10000 >= 25 * h * h)
cout << "Yes";
else
cout << "No";
}
B - Keep the Change
- 预估难度:
入门 - 标签:循环语句、条件分支
题意
高桥在 \(N\) 家商店进行了购物。他最初有 \(10000\) 日元。
在第 \(i\) 家商店,他购买了价值 \(A_i\) 日元的商品,并支付了 \(B_i\) 日元。保证 \(A_i \leq B_i\)。
如果 \(S_i =\) keep,表示他没有将找零的钱拿走;如果 \(S_i =\) take,表示他将找零的钱拿走了。
请计算他总共损失了多少钱。具体来说:
- 设 \(X\) 为高桥在目前方案下,最终实际拥有的金额;
- 设 \(Y\) 为假设他在每家商店都将找零的钱拿走时,最终可以拥有的金额。
请求出 \(Y - X\) 的值。
数据范围
- \(1 \leq N \leq 100\)
- \(1 \leq A_i \leq B_i \leq 100\)
- \(S_i\) 是
keep或take中的一种
思路
对于每次购物,实际购物只需要 \(A_i\) 元,如果支付了 \(B_i\) 元,商店就需要找零 \(B_i - A_i\) 元。
明显只有当 \(S_i =\) take 时,会造成 \(B_i - A_i\) 元的损失,用一个答案变量统计损失总和即可。
代码
void solve()
{
int n, ans = 0;
cin >> n;
while(n--)
{
int a, b;
string s;
cin >> a >> b >> s;
if(s == "keep") // 此时没有将找零的钱取走,造成 b-a 的亏损
ans += b - a;
}
cout << ans;
}
C - Adjacent Sums (easy)
- 预估难度:
普及- - 标签:枚举、数学
题意
题目 C 的题面与题目 E 相同,仅数据范围存在不同。
给定两个整数序列 \(A=(A_1,A_2,\dots,A_N)\) 和 \(B=(B_1,B_2,\dots,B_{N-1})\),其中的元素均为 \(0\) 到 \(M-1\) 之间的整数(包含 \(0\) 和 \(M-1\))。
序列 \(A\) 和 \(B\) 的长度分别为 \(N\) 和 \(N-1\)。
你可以对序列 \(A\) 执行以下操作任意次:
- 将序列中某个位置的数 \(A_i\) 加上 \(1\)。
请计算使序列 \(A\) 满足以下条件所需的最少操作次数。
- 对于每个 \(i=1,2,\dots,N-1\),\((A_i+A_{i+1})\) 除以 \(M\) 的余数等于 \(B_i\)。
可以证明,在本题的约束条件下,该条件总是可以被满足的。
数据范围
- \(2 \leq N \leq 2 \times 10^5\)
- \(\color{red}{M=2}\)
- \(0 \leq A_i, B_i \leq M-1\)
思路
由于 \((a + b) \bmod M = (a \bmod M + b \bmod M) \bmod M\),如果我们对同一个数字做 \(M\) 次操作,那么该数字除以 \(M\) 的余数会回到一开始的状态,相当于没进行过任何操作。
因此可以先得出一个结论:序列 \(A\) 中每个位置的数至多只会被操作 \(M-1\) 次。
对于 \(i\) 和 \(i+1\) 这两个位置,最终分别需要保证以下条件满足:
- ①:\((A_i+A_{i+1})\) 除以 \(M\) 的余数等于 \(B_i\)
- ②:\((A_{i+1}+A_{i+2})\) 除以 \(M\) 的余数等于 \(B_{i+1}\)
那么,如果我们能够将 \(A_i\) 的值确定下来不再改变,为了让条件①满足,我们只能够对 \(A_{i+1}\) 进行操作。
假设我们对 \(A_{i+1}\) 进行了 \(x\) 次操作,由于最终目标是要求出满足 \((A_i + A_{i+1} + x) \bmod M = B_i\) 的最小 \(x\) 的值,又由于 \(0 \le x \le M-1\),明显该限制下的答案是唯一的。
那么,我们便能够将 \(A_{i+1}\) 的值也确定下来,接下来就可以继续根据条件②来唯一确定 \(A_{i+2}\) 的值,依此类推,直到后面的所有数值全部确定。
回到整到题目里,我们只需要能够把 \(A_1\) 确定下来,那么 \(A_2, A_3, \dots, A_N\) 这些数字的最终情况就都能确定了,并且在过程中将每个数字的操作次数求和,便是最终的总操作次数。
至于如何确定 \(A_1\) 的值,由于本题 \(M=2\),也就是每个数字要么不操作,要么只会操作一次。对 \(A_1\) 直接枚举这两种情况即可。
时间复杂度 \(O(M\cdot N)\)。
代码
int n, m, a[200005], b[200005];
int c[200005]; // 辅助数组
void solve()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> a[i];
for(int i = 1; i < n; i++)
cin >> b[i];
int ans = 1e9;
for(int t = 0; t < m; t++) // 枚举 a[1] 的操作次数,仅为 0/1
{
for(int i = 1; i <= n; i++)
c[i] = a[i];
c[1] += t; // c[1] 先操作 t 次
int sum = t; // 总操作次数
for(int i = 1; i < n; i++) // c[i] 已确定,根据条件求 c[i+1] 的最终值
{
if((c[i] + c[i + 1]) % m == b[i]) // 条件已满足,c[i+1] 不需要操作
continue;
else // 否则,由于 m=2,只需要对 c[i+1] 操作一次即可符合条件
{
c[i + 1]++;
sum++;
}
}
ans = min(ans, sum);
}
cout << ans;
}
D - Concentric Circles
- 预估难度:
普及+/提高- - 标签:计算几何
题意
在 \(xOy\) 平面上,是否存在两个圆 \(C_1\) 和 \(C_2\) 满足以下所有条件?(注意:\(C_1\) 和 \(C_2\) 可以是完全相同的圆。)
- \(C_1\) 和 \(C_2\) 为同心圆。
- 点 \((P_x, P_y)\) 和点 \((Q_x, Q_y)\) 位于圆 \(C_1\) 的圆周上。(保证两点不同)
- 点 \((R_x, R_y)\) 和点 \((S_x, S_y)\) 位于圆 \(C_2\) 的圆周上。(保证两点不同)
多组数据。
数据范围
- \(1 \leq T \leq 5 \times 10^4\)
- \(-10^9 \leq P_x,P_y,Q_x,Q_y,R_x,R_y,S_x,S_y \leq 10^9\)
- \((P_x, P_y) \neq (Q_x, Q_y)\)
- \((R_x, R_y) \neq (S_x, S_y)\)
思路
如果在一个圆的圆周上取两个不同的点 \(A, B\),那么这两个点与圆心一定满足:
- \(A, B\) 的中垂线一定经过圆心
换句话说,只要在 \(A, B\) 的中垂线上随意取一个点 \(C\),一定能够画出一个以 \(C\) 作为圆心,且圆周经过 \(A, B\) 两个点的圆。
对于题目给定的四个点 \(P, Q, R, S\),分类讨论 \(P, Q\) 的中垂线与 \(R, S\) 的中垂线的关系:
- 两条中垂线不平行。这种情况下两条中垂线会相交于某一个点 \(D\)。那么只要以 \(D\) 作为圆心,就一定能画出两个圆并符合题意。
- 两条中垂线平行。
- 如果两条中垂线为同一条线(重合),明显圆心的取法无限多。
- 而如果平行且不为同一条线,此时明显无解。
综上,我们只需要判断给定的四个点所组成的两条中垂线是否平行,如果平行再判断是否共线,即可得出结论。
第一步,判断两条中垂线是否平行,这其实也等价于判断原本给定的两点连线 \(\overline{PQ}\) 以及 \(\overline{RS}\) 是否平行。
做法有很多。可以先将 \(\overrightarrow{PQ}\) 以及 \(\overrightarrow{RS}\) 描述为向量(即计算两点坐标 \(x, y\) 各自相减的结果),然后通过约分将其描述为最简向量,判断是否共向或反向即可。
或者直接借助计算几何的知识点,通过判断向量叉积 \(\overrightarrow{PQ} \times \overrightarrow{RS} = 0\) 即可确定两向量是否共线。
如果中垂线平行,进入第二步,判断两条中垂线是否共线。
如果中垂线共线,说明原本的两条线 \(\overline{PQ}\) 和 \(\overline{RS}\) 的中点均在中垂线上。
记 \(\overline{PQ}\) 的中点为 \(X\),记 \(\overline{RS}\) 的中点为 \(Y\),如果将两个中点连线成为 \(\overline{XY}\),明显这就是中垂线。
也就是说,我们只需要判断 \(\overline{XY}\) 是否垂直于 \(\overline{PQ}\) 和 \(\overline{RS}\) 即可(判断其中一条就行)。
同样将 \(\overrightarrow{XY}\) 描述为向量,如果是数学方法,可以通过计算直线斜率的乘积是否为 \(-1\) 来判断垂直,但需注意浮点误差以及斜率不存在的情况。
如果借助计算几何的知识点,通过判断向量点积 \(\overrightarrow{XY} \cdot \overrightarrow{PQ} = 0\) 即可确定两向量是否垂直。
在这里,如果要直接计算 \(P,Q\) 的中点 \(X\) 以及 \(R, S\) 的中点 \(Y\) 的话,坐标可能会出现 x.5 的小数情况。为避免小数,可以将一开始给定的所有点的坐标全部先 \(\times 2\),相当于将坐标系整体放大 \(2\) 倍,来保证中点的计算不会出现小数。
单组数据时间复杂度 \(O(1)\),以下代码借助点乘与叉乘进行判断。
代码(单组数据)
struct Point // 点 / 向量
{
ll x, y;
};
ll Dot(Point a, Point b) // 向量点乘
{
return a.x * b.x + a.y * b.y;
}
ll Cross(Point a, Point b) // 向量叉乘
{
return a.x * b.y - a.y * b.x;
}
void solve()
{
Point P, Q, R, S, X, Y;
cin >> P.x >> P.y;
cin >> Q.x >> Q.y;
cin >> R.x >> R.y;
cin >> S.x >> S.y;
// 放大成两倍 防止中点计算出现小数
P.x *= 2, P.y *= 2;
Q.x *= 2, Q.y *= 2;
R.x *= 2, R.y *= 2;
S.x *= 2, S.y *= 2;
// 计算 PQ 和 RS 的中点,记作 X Y
X = {(P.x + Q.x) / 2, (P.y + Q.y) / 2};
Y = {(R.x + S.x) / 2, (R.y + S.y) / 2};
Point PQ, RS, XY; // 计算对应向量
PQ = {Q.x - P.x, Q.y - P.y}; // 点的顺序取反不影响判断
RS = {S.x - R.x, S.y - R.y};
XY = {Y.x - X.x, Y.y - X.y};
if(Cross(PQ, RS) == 0 && Dot(PQ, XY) != 0)
cout << "No\n";
else
cout << "Yes\n";
}
E - Adjacent Sums (hard)
- 预估难度:
提高 - 标签:数学、STL、差分、前缀和
题意
题目 C 的题面与题目 E 相同,仅数据范围存在不同。
给定两个整数序列 \(A=(A_1,A_2,\dots,A_N)\) 和 \(B=(B_1,B_2,\dots,B_{N-1})\),其中的元素均为 \(0\) 到 \(M-1\) 之间的整数(包含 \(0\) 和 \(M-1\))。
序列 \(A\) 和 \(B\) 的长度分别为 \(N\) 和 \(N-1\)。
你可以对序列 \(A\) 执行以下操作任意次:
- 将序列中某个位置的数 \(A_i\) 加上 \(1\)。
请计算使序列 \(A\) 满足以下条件所需的最少操作次数。
- 对于每个 \(i=1,2,\dots,N-1\),\((A_i+A_{i+1})\) 除以 \(M\) 的余数等于 \(B_i\)。
可以证明,在本题的约束条件下,该条件总是可以被满足的。
数据范围
- \(2 \leq N \leq 2 \times 10^5\)
- \(\color{red}{3 \leq M \leq 10^9}\)
- \(0 \leq A_i, B_i \leq M-1\)
思路
同 C 题,首先发现序列 \(A\) 中每个位置的数至多只会被操作 \(M-1\) 次,并且只要 \(A_1\) 确定,那么整个序列 \(A\) 其它数值也就都确定了。
那么,我们可以先假设 \(A_1\) 不做操作,以此推导出整个序列 \(A\) 其它位置的最终数值,并将每个位置目前的操作次数记作序列 \(C = \{C_1, C_2, \ldots, C_N\}\)。
- 即 \(C_1 = 0\)
- 对于 \(i = 2 \sim N\),由于需要满足 \((A_{i-1} + C_{i-1}) + (A_i + C_i) \equiv B_{i-1} \ (\bmod M)\),因此 \(C_i \equiv B_{i-1} - (A_{i-1} + C_{i-1} + A_i) \ (\bmod M)\)
考虑对 \(A_1\) 做一次操作,将 \(A_1\) 变为 \(A_1 + 1\)。为了让 \(A_1 + A_2 \equiv B_1 \ (\bmod M)\) 恒满足,那么 \(A_2\) 就要对应变成 \(A_2 - 1\)(在模 \(M\) 的意义下)。\(A_2\) 一旦减少,同理 \(A_3\) 就会对应增加,\(A_4\) 又会对应减少,……
因此,如果 \(A_1\) 做了 \(x\) 次操作,那么对于 \(i = 2 \sim N\) 的每个 \(A_i\) 都将会变为 \(A_i + (-1)^{i-1}x \ (\bmod M)\)。
分类讨论 \(i\) 的奇偶性:
- 如果 \(i\) 是奇数,那么随着 \(A_1\) 操作次数变多,其最终数值 \(A_i\) 也会对应增大,表示我们需要在 \(i\) 位置上做的操作次数会变多。由于在 \(A_1\) 不做任何操作时,我们需要对 \(A_i\) 做 \(C_i\) 次操作。也就是说,只要目前 \(A_1\) 的操作次数不超过 \(M-C_i-1\) 次,那么每多一次,\(i\) 位置的操作次数就也得对应多做一次。而一旦 \(A_1\) 的操作次数 \(x\) 超过了 \(M-C_i-1\) 次,根据模运算性质,超过 \(M\) 次与做 \(0\) 次相同,因此操作次数可以使用 \(x - (M-C_i)\) 计算,相当于总操作次数忽然减少了 \(M\) 次。
- 也就是说,从 \(A_1\) 的第 \(M-C_i\) 次操作开始,总操作次数会减少 \(M\) 次。
- 如果 \(i\) 是偶数,那么随着 \(A_1\) 操作次数变多,其最终数值 \(A_i\) 会对应减少,表示我们需要在 \(i\) 位置上做的操作次数会变少。由于在 \(A_1\) 不做任何操作时,我们需要对 \(A_i\) 做 \(C_i\) 次操作。也就是说,只要目前 \(A_1\) 的操作次数不超过 \(C_i\) 次,那么每多一次,\(i\) 位置的操作次数就能对应少做一次。而一旦 \(A_1\) 的操作次数 \(x\) 超过了 \(C_i\) 次,根据模运算性质,此时的操作次数需要使用 \(M - (x - C_i)\) 计算,相当于总操作次数忽然增加了 \(M\) 次。
- 也就是说,从 \(A_1\) 的第 \(C_i+1\) 次操作开始,总操作次数会增加 \(M\) 次。
我们假设每过一个单位时间对 \(A_1\) 操作一次(也就是把 \(A_1\) 的操作次数看作经过时间),那我们就能将这些关键时间结点对答案的贡献,按照”时间-影响“的关系存储起来(可以借助 map)。接下来讨论总数 \(N\) 的奇偶性:
- 如果 \(N\) 为偶数,说明奇数位置和偶数位置的数字个数相同,那么每过一个单位时间,操作次数 \(-1\) 和 \(+1\) 的位置个数是一致的,操作总数不会受到任何影响。
- 而如果 \(N\) 为奇数,说明奇数位置比偶数位置的数字个数多一个,那么每过一个单位时间,操作次数 \(+1\) 的位置个数会比 \(-1\) 恰好多一个,也就是说,经过多少个单位时间,总操作次数就会额外多出来多少次。注意该种情况对答案的影响即可。
最后按时间顺序处理每个事件发生后对答案的影响,过程中取总操作次数的最小值就是最终答案。
时间复杂度 \(O(N\log N)\)。
代码
int n, m;
ll a[200005], b[200005], c[200005];
void solve()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> a[i];
for(int i = 1; i < n; i++)
cin >> b[i];
ll cost = 0; // 统计第一个位置不做操作时的总操作次数
c[1] = 0;
for(int i = 2; i <= n; i++)
{
c[i] = (b[i - 1] - (a[i - 1] + c[i - 1] + a[i])) % m;
c[i] = (c[i] + m) % m; // 防止负数影响
cost += c[i];
}
ll ans = cost; // 初始答案 = 第一个位置不做操作时的总操作次数
map<ll, ll> events;
for(int i = 2; i <= n; i++)
{
// 奇数位置上的初始操作次数为 0,说明在整个操作过程中其操作数的变化方式不会改变
if(i % 2 == 1 && c[i] == 0)
continue;
// 偶数位置上的初始操作次数为 m-1,说明在整个操作过程中其操作数的变化方式不会改变
if(i % 2 == 0 && c[i] == m - 1)
continue;
// 记录特殊事件
if(i % 2 == 1)
events[m - c[i]] -= m;
else
events[c[i] + 1] += m;
}
for(auto p : events)
{
cost += p.second; // 该时间对应的所有事件对总操作次数的影响
if(n % 2 == 0)
ans = min(ans, cost);
else // 如果 n 为奇数,每过 1 个单位时间,总操作次数会额外 +1
ans = min(ans, cost + p.first);
}
cout << ans;
}

浙公网安备 33010602011771号