top1编程
← 返回题目
题解

Blah数集

1 条题解

  • 0
    @ 2026-8-6 2:22:20

    P4845 Blah数集(提高)

    解题思路

    **第一步,理解集合的生成规则。**集合Ba里第一个数是a,然后每个数x都会“生”出两个新数:2x+1 和 3x+1。把生成的数也放进集合,集合就越来越大。现在要求把集合里的数从小到大排好序,输出第n个。

    **第二步,怎么能从小到大生成呢?**我们用一个数组q按从小到大的顺序保存已经生成的数,q[0]=a。再用两个“小箭头”p2和p3:p2指向还没有用来生成2x+1的那个数,p3指向还没有用来生成3x+1的那个数。因为数组是升序的,所以2q[p2]+1是所有“2x+1式”里最小的,3q[p3]+1是所有“3x+1式”里最小的,取这两者中更小的那个,就是下一个应该加入集合的数。

    **第三步,处理重复的数。**有时2x+1和3y+1会算出同一个数,比如4可以由21+1=3和31+1=4得到(不重复),但也可能两边算出一样的数。遇到这种情况,哪个箭头的算式等于刚加入的数,就把哪个箭头往后移,这样才能保证集合里没有重复,而且不会漏数。

    **第四步,注意数字会很大。**n最大是1000000,集合里的数会变得非常大,可能超过int能存的范围,所以要用long long来存数组。数组的长度就是n,开1000005个位置。

    **举例子验证。**a=1时,前几个数是 1、3、4、7、9、10、13、15……其中1生成3和4,3生成7和10,4生成9和13……按升序排,第8个是15,和样例一致。

    参考代码

    // P4845 Blah数集:双指针生成第n小的元素,每个元素x产生2x+1和3x+1
    #include <iostream>
    #include <cstdio>
    using namespace std;
    int a, n;
    long long q[1000005];  // 数组存升序的集合元素,n最大1000000
    int main() {
        scanf("%d%d", &a, &n);
        q[0] = a;              // 基a是第一个元素
        int p2 = 0, p3 = 0, cnt = 1;
        while (cnt < n) {
            long long v2 = 2 * q[p2] + 1;  // 由2x+1产生的候选
            long long v3 = 3 * q[p3] + 1;  // 由3x+1产生的候选
            long long v = v2 < v3 ? v2 : v3;
            q[cnt++] = v;
            if (v2 == v) p2++;  // 指针前进,跳过重复
            if (v3 == v) p3++;
        }
        printf("%lld\n", q[n - 1]);
        return 0;
    }
    

    复杂度分析

    每个数只被加入数组一次,两个箭头p2、p3最多各移动n次,所以总时间是O(n)。n最大是1000000,循环一百万次完全没问题。空间上需要一个能装下n个数的long long数组,约8MB内存,也足够。

    • 1