题解:洛谷 P2035 iCow

【题目来源】

洛谷:P2035 [USACO08JAN] iCow B - 洛谷

【题目描述】

被无止境的农活压榨得筋疲力尽后,FarmerJohn 打算用他在 MP3 播放器市场新买的 iCow 来听些音乐,放松一下。FJ 的 iCow 里存了 \(N(1 \le N \le 1{,}000)\) 首曲子,按 \(1..N\) 依次编号。至于曲子播放的顺序,则是按一个 FarmerJohn 自己设计的算法来决定:

  • \(i\) 首曲子有一个初始权值 \(R_i\ (1 \le R_i \le 10{,}000)\)
  • 当一首曲子播放完毕,接下来播放的将是所有曲子中权值最大的那首(如果有两首或多首曲子的权值相同,那么这些曲子中编号最小的那首会被选中)。
  • 一首曲子在播放结束后,它的权值会被平均地分给其他 \(N-1\) 首曲子,它本身的权值清零。
  • 如果一首曲子的权值无法被平均分配(也就是说,无法被 \(N-1\) 整除),那么被 \(N-1\) 除的余数部分将会以 \(1\) 为单位,顺次分配给排名靠前的曲子(也就是说,顺序为曲目 \(1\) 、曲目 $2 \cdots $ 依次下去。当然,刚播放过的那首曲子需要被跳过),直到多出的部分被分配完。

在选定的下一首曲子播放完毕后,这个算法再次被执行,调整曲子的权值,并选出再接下来播放的曲目。

请你计算一下,按 FJ 的算法,最先播放的 \(T\ (1 \le T \le 1000)\) 首曲子分别是哪些。

【输入】

\(1\) 行有两个用空格隔开的整数:\(N\)\(T\)

\(2\) 至第 \(N+1\) 行,第 \(i+1\) 行为 \(1\) 个整数 \(R_i\)

【输出】

输出共 \(T\) 行,第 \(i\) 行输出一个整数,表示 iCow 播放的第 \(i\) 首曲子。

【输入样例】

3 4
10
8
11

【输出样例】

3
1
2
3

【核心思想】

  1. 问题分析:给定 \(N\) 首曲子,每首曲子有初始权值 \(R_i\),需要按 FJ 的算法依次选出 \(T\) 首要播放的曲子。每次选曲规则为:选择权值最大的曲子(权值相同时选编号最小的),播放后将其权值清零并平均分配给其他 \(N-1\) 首曲子(余数按编号顺序分配给靠前的曲子)。这是一个模拟问题,关键在于严格按照题目规则逐步模拟 \(T\) 次选曲过程。

  2. 算法选择

    • 直接模拟:每次遍历 \(N\) 首曲子找到权值最大者,然后按规则重新分配权值
    • 编号优先处理:权值相同时选编号最小的,通过在遍历中严格使用 > 比较(而非 >=)自然实现
  3. 关键步骤

    • 初始化:读取 \(N\)(曲子数量)、\(T\)(播放次数),以及 \(R[1..N]\)(初始权值),同时记录每首曲子的编号
    • 外层循环(执行 \(T\) 次):
      • 选曲:遍历 \(i\)\(1\)\(N\),找到满足 \(R_i > \text{当前最大值}\) 的曲子,记录其编号 \(\text{maxnum}\)
      • 输出:输出 \(\text{maxnum}\) 作为本次播放的曲子编号
      • 计算分配值
        • \(\text{avg} = R_{\text{maxnum}} / (N-1)\):整除部分,每首其他曲子分配 \(\text{avg}\)
        • \(\text{ys} = R_{\text{maxnum}} \% (N-1)\):余数部分,需要额外分配
      • 清零\(R_{\text{maxnum}} = 0\)
      • 分配整除部分:遍历 \(i\)\(1\)\(N\),跳过 \(\text{maxnum}\),其余每首曲子 \(R_i += \text{avg}\)
      • 分配余数部分:遍历 \(i\)\(1\)\(N\),跳过 \(\text{maxnum}\),每首曲子 \(R_i += 1\),同时 \(\text{ys}--\),直到 \(\text{ys} = 0\)
    • 重复上述过程 \(T\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \times N)\),每次选曲需要 \(O(N)\) 遍历找最大值,分配权值需要 \(O(N)\),共执行 \(T\)
    • 空间复杂度:\(O(N)\),存储 \(N\) 首曲子的权值和编号
  5. 模拟的核心思想

    • 严格按规则执行:模拟题的核心是准确理解题意,将文字描述转化为代码逻辑,不省略任何细节
    • 编号优先的自然实现:使用 > 而非 >= 比较,确保权值相同时编号小的优先被选中
    • 余数分配的循环处理:余数按编号顺序分配,遇到刚播放过的曲子跳过,用简单循环配合 continue 实现
    • 数据范围可控\(N \le 1000\)\(T \le 1000\)\(O(T \times N)\) 完全在可接受范围内,无需优化
    • 适用于规则明确、步骤清晰、数据范围较小的过程模拟类问题

【解题思路】

【算法标签】

普及- #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, T;
struct node {
    int num, r;
}a[1005];
int main()
{
    cin >> n >> T;  // 输入n和T
    for (int i=1; i<=n; i++) {  // 输入n首曲子的权值
        cin >> a[i].r;
        a[i].num = i;  // 以及编号
    }

    while (T--) {  // 遍历T次循环
        int maxn = -1e9, maxnum=-1;  // 定义最大权值和其编号
        for (int i=1; i<=n; i++) {  // 遍历n首曲子
            if (a[i].r>maxn) {  // 找到最大的权值以及对应的编号
                maxn = a[i].r;
                maxnum = i;
            }
        }
        cout << maxnum << endl;  // 输出最大权值的编号
        int avg = a[maxnum].r / (n-1);  // 计算整除的平均值
        int ys = a[maxnum].r % (n-1);  // 计算余数
        a[maxnum].r = 0;  // 将最大权值的曲子的权值置为0
        for (int i=1; i<=n; i++) {  // 遍历n首曲子,将平均值加到n-1首曲子上
            if (i==maxnum) continue;
            a[i].r += avg;
        }
        for (int i=1; i<=n; i++) {  // 从第1首开始,将余数以1为单位分配到每首曲子上
            if (ys==0) break;
            if (i==maxnum) continue;
            a[i].r += 1;
            ys--;  // 每分配一个,余数减1,直至为0
        }
    }
    return 0;
}

【运行结果】

3 4
10
8
11
3
1
2
3
posted @ 2026-08-17 09:46  团爸讲算法  阅读(5)  评论(0)    收藏  举报