题目描述

给定一个长度为 的序列 ,请你构造一个序列 ,序列 满足以下条件:

  1. 序列 的长度为
  2. 对于任意 ,满足
  3. 对于任意 ,满足
  4. 对于任意 ,满足 (即 中所有元素需互不相同)。

若有多个不同的答案, 输出任意一个即可。

解题思路

这是一个构造性问题。我们需要为序列 中的每个元素 找到一个对应的 ,满足一系列条件,其中最核心的是取模和唯一性约束。

1. 分析核心约束

条件 (a[i] + b[i]) mod i = 0 意味着 a[i] + b[i] 必须是 i 的一个倍数。换句话说,b[i] 必须满足 b[i] ≡ -a[i] (mod i)

对于每一个 ,都存在一个最小的正整数 b[i] 满足这个同余关系。我们把这个最小的可能值称为基础值,记作

  • rem = a[i] % i
  • 如果 rem == 0,则 本身就是 的倍数,那么 也必须是 的倍数。最小的正数 就是
  • 如果 rem != 0,那么为了让 a[i] + b[i] 凑成 的倍数,我们需要 来补足 rem 的差距,即

因此,任何满足条件的 都必须具有以下形式: ,其中 是任意非负整数。

2. 贪心构造策略

现在的问题转化为:为每个 选择一个合适的 ,使得所有最终的 互不相同。

既然题目允许输出任意解,我们可以采用贪心策略来构造。

  1. 分组:首先,我们计算出所有 的基础值 。然后,我们将所有索引 按照它们的 值进行分组。例如,如果 ,那么索引 2 和 7 就在同一组。

  2. 解决冲突:显然,同一组内的索引,它们的 值初始候选项是相同的,必然会产生冲突。我们需要逐个为它们重新赋值。

    • 我们维护一个全局的哈希集合 used_b_values,用来存放所有已经分配出去的 值。
    • 我们遍历每个分组。对于分组内的每一个索引 idx
      • 我们从它的基础值 current_b = b_{idx, base} 开始检查。
      • 循环检查:如果 current_b 已经存在于 used_b_values 中,说明这个值已经被占用了,我们就尝试下一个可能的值,即 current_b = current_b + idx
      • 我们重复这个过程,直到找到一个没有被占用的 current_b
      • 将这个找到的 current_b 作为 的最终值,并将其添加到 used_b_values 集合中。

通过这种方式,我们为每个索引 i 都找到了一个满足条件的、且与之前所有值都不同的 ,从而成功构造出整个序列

代码

#include <iostream>
#include <vector>
#include <numeric>
#include <map>
#include <set>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    cin >> n;

    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

    map<int, vector<int>> groups;
    for (int i = 1; i <= n; ++i) {
        int rem = a[i - 1] % i;
        int b_base = (rem == 0) ? i : i - rem;
        groups[b_base].push_back(i);
    }

    set<long long> used_b_values;
    vector<long long> b(n + 1);

    for (auto const& [b_base, indices] : groups) {
        for (int idx : indices) {
            long long current_b = b_base;
            while (used_b_values.count(current_b)) {
                current_b += idx;
            }
            b[idx] = current_b;
            used_b_values.insert(current_b);
        }
    }

    for (int i = 1; i <= n; ++i) {
        cout << b[i] << (i == n ? "" : " ");
    }
    cout << endl;

    return 0;
}
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = sc.nextInt();
        }

        Map<Integer, List<Integer>> groups = new TreeMap<>();
        for (int i = 1; i <= n; i++) {
            int rem = a[i - 1] % i;
            int b_base = (rem == 0) ? i : i - rem;
            groups.computeIfAbsent(b_base, k -> new ArrayList<>()).add(i);
        }

        Set<Long> usedBValues = new HashSet<>();
        long[] b = new long[n + 1];

        for (Map.Entry<Integer, List<Integer>> entry : groups.entrySet()) {
            int b_base = entry.getKey();
            List<Integer> indices = entry.getValue();
            for (int idx : indices) {
                long currentB = b_base;
                while (usedBValues.contains(currentB)) {
                    currentB += idx;
                }
                b[idx] = currentB;
                usedBValues.add(currentB);
            }
        }

        for (int i = 1; i <= n; i++) {
            System.out.print(b[i] + (i == n ? "" : " "));
        }
        System.out.println();
    }
}
import sys
from collections import defaultdict

def solve():
    try:
        n = int(sys.stdin.readline())
        a = list(map(int, sys.stdin.readline().split()))
    except (IOError, ValueError):
        return

    groups = defaultdict(list)
    for i in range(1, n + 1):
        rem = a[i - 1] % i
        b_base = i if rem == 0 else i - rem
        groups[b_base].append(i)

    used_b_values = set()
    b = [0] * (n + 1)

    # Sorting keys is not necessary for correctness but makes behavior deterministic
    for b_base in sorted(groups.keys()):
        indices = groups[b_base]
        for idx in indices:
            current_b = b_base
            while current_b in used_b_values:
                current_b += idx
            b[idx] = current_b
            used_b_values.add(current_b)

    print(*b[1:])

solve()

算法及复杂度

  • 算法:贪心构造

  • 时间复杂度,其中 是序列的长度, 是解决所有冲突所需的总探测(增加)次数。

    • 的部分来自于将 个索引分组(使用 std::map)和向 std::set 中插入 个最终值。
    • 来自于在 while 循环中检查哈希集合的开销。在大多数情况下, 的值不会太大,使得该算法能够高效运行。
  • 空间复杂度

    • 需要 的空间来存储分组信息、已使用的 值集合以及最终的结果数组