题解:B4395 [常州市赛 2025] 金币

题解:B4395 [常州市赛 2025] 金币

前言

题目传送门

思路讲解

一个十分经典的约瑟夫问题。

我们可以想象淘汰的过程,相当于每 \(k\) 个人中有1人被淘汰,留下来 \(k-1\) 个人,而且多余的人也会有一人被淘汰,那么我们就可以设置一个答案变量 \(x\),表示答案位置,那么根据刚才的推论,答案每次加上 $\left \lceil \frac{x}{k-1} \right \rceil $,直到其加上后大于 \(n\) 就输出。

但是很明显会超时,当 \(k\) 非常大的时候,时间复杂度接近 \(O(n)\)\(k\) 较小是时间复杂度是 \(O(log\) \(n)\) 或者 \(O(k\) \(log\) \(n)\),直接炸掉。

我们需要去优化时间复杂度,根据每次 \(x\) 加上的值去分情况解决:

  • 当这个值 \(<k\) 时,需要求出 \(x\) 要多久才能使加上的数加 1。
  • 当这个值 \(\ge k\) 时,暴力出奇迹。

Code

#include <bits/stdc++.h>
#define int long long // 不开longlong见祖宗
using namespace std;
int n, k;
// 计算下一个安全位置的函数
inline int add(int u)
{
    // 这个公式用于计算在当前淘汰规则下,下一个不会被淘汰的位置
    return u + 1 + (u - 1) / (k - 1);
}
signed main()
{
    cin >> n >> k; //输入
    int x = 1; // 初始位置设为1
    // 第一个循环:尝试快速逼近最终解,最多循环k次
    for (int i = 0; i < k; i++)
    {
        // cur表示在当前步长(i+1)下可以跳过的最大步数
        int cur = k - 1 - (x - 1) / (i + 1);
        // 如果跳过cur步后会超过或等于n,直接计算最终位置
        if (x + cur * (i + 1) >= n)
        {
            // 计算最终位置:x加上剩余步数
            x += (n - x) / (i + 1) * (i + 1);
            cout << x << endl; // 输出结果
            return 0;
        }
        else
        {
            // 否则,更新x的位置
            x += cur * (i + 1);
        }
    }
    // 第二个循环:如果第一个循环没有找到解,继续逐步计算
    while (1)
    {
        // 计算下一个安全位置
        if (add(x) > n)
        {
            // 如果下一个位置超过n,当前x就是解
            break;
        }
        // 否则,更新x
        x = add(x);
    }
    cout << x << endl; // 输出最终结果
    return 0;
}

时空复杂度分析

时间复杂度为 \(O(k\) \(log\) \(n)\),部分数据还是会超,只不过数据很水

空间复杂度就存三个变量,可以忽略成没有

posted @ 2025-11-30 15:49  fengjunxiao2014  阅读(3)  评论(0)    收藏  举报