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\)keeptake 中的一种

思路

对于每次购物,实际购物只需要 \(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;
}
posted @ 2026-07-18 22:20  StelaYuri  阅读(53)  评论(0)    收藏  举报