1.题目描述:点击打开链接
2.解题思路:本题利用打表+二分查找。仔细观察后会发现如下规律:
(1):序列中,相同元素的个数构成的序列仍然是原序列;
(2):如果我们按照个数来分类,可以写成下面的形式:
个数 元素
1 1
2 2, 3
3 4, 5
4 6, 7, 8
5 9, 10, 11
6 12, 13, 14, 15
7 16, 17, 18, 19
......
可以发现:1. sum[i]是个数为i的所有元素中最后一个元素;2.第i行中的所有元素也是i这个元素在原始序列中的所有下标。
(3)观察sum{i*a[i]}可以发现,这个计算结果就是我们要的答案。因此可以考虑计算第n项对应的元素a[n],然后求sum{i*a[i]|1<=i<=n}即可。
那么第一个问题就是,已知n,如何获得a[n]?考虑到第n项对应的元素a[n]不是很大,可以事先测试一下当n是多少的时候,sum[n]刚好超过10^9,经过试验会发现,N=440000即可。这样,可以事先打表所有前N项的元素。由于sum数组也可以理解为当个数为i时候,已经写了几个不同的数。那么我们可以找一下n在sum中的下标,即k=lower_bound(sum+1,sum+N,n)-sum。这样,我们就可以找到个数为k-1时候的最后一个元素,即sum[k-1](规律2第一条的应用)。同时,根据规律(2)的第二条还可以知道,第n项的元素其实就是k。
那么第二个问题就是,如何高效计算sum{i*a[i]},因为i最大可以达到10^9,遍历一遍肯定会超时,但是仔细观察后会发现,当a[i]固定时候,所有的i其实构成了一个等差数列,这其实还是规律2第二条的应用:a[i]的所有下标显然是一个公差为1的等差数列,而且个数恰好是a[i]个,因此,就可以用O(1)时间计算出a[i]对应的这段等差数列的和,然后再和a[i]相乘。而这个过程也是可以预先打表处理的。不妨用S[i]表示前i项乘积和的结果。这样,最终的答案ans=S[k-1]+k*sum{i|sum[k-1]+1<=i<=n}。注意,最后一段之所以不用等差数列公式求解是因为中间结果可能会爆long long,但考虑到这段的个数只有不超过4000个,可以直接逐项相加取模。
3.代码:
#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<complex>
#include<functional>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
#define me(s) memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int, int> P;
const int N=440000+10;
const int MOD=1000000007;
int a[N];
ll sum[N]; //sum[i]表示序列前i项的和
ll S[N];//S[i]表示前i项的乘积和
void init()
{
a[1]=1,a[2]=2;a[3]=2;
int cnt=4;
for(int i=3;i<N;i++)
{
for(int j=0;j<a[i];j++)
a[cnt++]=i;
if(cnt>=N)break;
}
sum[0]=0;
for(int i=1;i<N;i++)
sum[i]=sum[i-1]+a[i];
S[0]=0;
ll first=1;
for(int i=1;i<N;i++)
{
ll s=first*a[i]%MOD+(a[i]-1)*a[i]/2;
s%=MOD;
S[i]=(S[i-1]+s*i)%MOD;
first=(first+a[i])%MOD;
}
}
int main()
{
int T;
init();
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
int k=lower_bound(sum+1,sum+N,n)-sum;
ll last=sum[k-1];
ll val=k;
ll ans=0;
for(int i=last+1;i<=n;i++)//逐项相加来求解最后一段的和
ans=(ans+i)%MOD;
ans=ans*val%MOD;
ans=(ans%MOD+S[k-1])%MOD;
printf("%d\n",ans);
}
}