HDU 5439 Aggregated Counting (2015年长春赛区网络赛C题)

来源:互联网 发布:月度m2数据 编辑:程序博客网 时间:2024/04/29 09:14

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);    }}

0 0
原创粉丝点击