1 条题解

  • 0
    @ 2026-10-26 7:13:55

    思路与算法

    把题目给出的序列记为g,位置从1开始。数字v连续出现g(v)次,起始1出现1次,2出现2次,后面的块按题目规则继续。因此last(N)=sum(g(v),1<=v<=N)。再看last(last(N)):它是序列前last(N)项之和,而这段恰好包含数值1到N的完整块。按块求和得到目标恒等式last(last(N))=sum(v*g(v),1<=v<=N)。

    不生成十亿项,而把右边的加权和按g的相等值块聚合。若位置l到r的g值都等于v,其贡献为v*(l+r)*(r-l+1)/2。预处理每个块的终点end[v]和完整块贡献的前缀pref[v],查询时二分第一个end[v]>=N,把此前完整块加上当前块从end[v-1]+1到N的等差位置和即可。

    预处理所需的块长度g(v),也来自同一序列,但只需读到块编号v的位置。程序保留种子[1,2,2],用当前输出值和本块剩余次数,按需逐项生成到下一个需要的g(v),不把整个长块追加到十亿位置。所有查询先读入,处理到最大N所处的块即可。模数为1000000007,除2用其模逆500000004,每次乘法后取模,避免存放巨大的真实双重last。

    正确性说明

    每个正整数v的总出现次数是g(v),所以最后一个N的位置是各个数值1..N的块长度之和。这说明对任意x,last(x)=sum(g(i),1<=i<=x)。令x=last(N),则范围1..last(N)正好按数值1..N完整分块,每块的元素和为v*g(v),证明双重last的加权恒等式。

    按位置聚合加权和时,g(i)在块[l,r]内恒等于v,所以每项ig(i)=vi,完整块贡献就是所给等差和。完整块前缀按顺序累加,恰覆盖从1到块终点的所有加权项。块终点严格递增,二分得到包含N的唯一块;完整前缀与当前不完整块互不重叠且覆盖1..N,因此查询值正确。

    按需生成与题目逐项输出同一序列:当前值和剩余次数描述下一个应输出的块。输出完一块后,下一块长度取已生成位置上的g(v)。长度查询总是已知,因为开始值v的块前,已输出last(v-1)>=v个位置(v>=3时由前面各块长度至少2得到,种子情况直接成立),因此访问安全。逐项延迟输出只影响存储时机,不改变序列。

    最后,模数为奇数且2*500000004模该数等于1,所以用此数替代除2保持等差和余数,逐乘逐模与整数完整求和再取模等价。故输出目标余数。

    复杂度

    令V为满足end[V]>=最大查询N的最小块编号。按需序列生成、块边界和前缀数组的分配及填充共O(V),不生成end[V]个位置。T个查询各O(log(V+1)),含读取和初始化总时间O(V+T log(V+1)),额外空间O(V+T)。在完整上界N=10^9实际需438744块,已独立计算并以最大查询实际执行。原值可能远超64位,但代码只保留边界与模后贡献;每次模后乘法的两因子不超过约10^9量级,long long可安全容纳。

    易错点

    1. 输出要求是last(last(N)),不是只求last(N);公开样例N=3的答案11印证双重查询。
    2. 位置从1开始,查询完整块边界时不能多算下一块。
    3. g(v)是块长度,end[v]是该块终点,二者含义不能混淆。
    4. 按需生成只保存到所需块编号的序列项,直接把块展开到10^9会超资源。
    5. 等差和先模乘再用逆2,不能先取模后普通整数除2;每一步乘法后取模。
    6. 覆盖N=1、前100个直接双重末位置、块边界前后、T=2000和最大N,输出每组一个整数。

    参考代码(C++11)

    #include <algorithm>
    #include <iostream>
    #include <vector>
    using namespace std;
    using ll = long long;
    const ll MOD = 1000000007, INV2 = 500000004;
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int T;
        cin >> T;
        vector<int> ask(T);
        int maxN = 0;
        for (int &x : ask) {
            cin >> x;
            maxN = max(maxN, x);
        }
        vector<int> g{0, 1, 2, 2};
        vector<ll> end{0}, pref{0};
        int value = 3, left = 2;
        for (int v = 1; end.back() < maxN; ++v) {
            while (v >= (int)g.size()) {
                g.push_back(value);
                if (--left == 0) {
                    ++value;
                    left = g[value];
                }
            }
            ll l = end.back() + 1, rr = end.back() + g[v];
            ll add = (l + rr) % MOD * g[v] % MOD * INV2 % MOD * v % MOD;
            end.push_back(rr);
            pref.push_back((pref.back() + add) % MOD);
        }
        for (int N : ask) {
            int v = lower_bound(end.begin() + 1, end.end(), N) - end.begin();
            ll l = end[v - 1] + 1, c = N - end[v - 1];
            ll part = (l + N) % MOD * (c % MOD) % MOD * INV2 % MOD * v % MOD;
            cout << (pref[v - 1] + part) % MOD << '\n';
        }
    }
    
    • 1

    信息

    ID
    1685
    时间
    1000ms
    内存
    256MiB
    难度
    提高
    标签
    递交数
    1
    已通过
    1
    上传者