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

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值