1 条题解
-
0
思路与算法
把题目给出的序列记为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可安全容纳。
易错点
- 输出要求是last(last(N)),不是只求last(N);公开样例N=3的答案11印证双重查询。
- 位置从1开始,查询完整块边界时不能多算下一块。
- g(v)是块长度,end[v]是该块终点,二者含义不能混淆。
- 按需生成只保存到所需块编号的序列项,直接把块展开到10^9会超资源。
- 等差和先模乘再用逆2,不能先取模后普通整数除2;每一步乘法后取模。
- 覆盖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