HDU 6304 Chiaki Sequence Revisited——计数问题

题意:

n=1,2时,a[n] =1;

n>=3时,a[n] = a[n-a[n-1]] + a[n-1-a[n-2]];

现在给定一个n(1<=n<=1e18),求前a的前n项和,结果mod(1e9+7)

思路:

大体思路:找规律->求出a[n]->求出a的前n项和

详细思路:

打表打出a后发现序列是这样的 1 1 2 2 3 4 4 4 5 6 6 7 8 8 8 8 9 10 10 ...

我们把每个数值的出现次数打出来,得到:

数值:        1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

出现次数:2 2 1 3 1 2 1 4 1  2   1   3   1   2   1   5...

1的出现次数破坏了我们接下来要讲的规律,所以我们将a[1] = 1拿出来单独讨论,找剩余元素的规律

再次将每个数值的出现次数打出来,得到:

数值:        1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

出现次数:1 2 1 3 1 2 1 4 1  2   1   3   1   2   1   5...

这是后就发现规律了,前2 ^ i个元素其实就是把前2^(i-1)个元素复制两次,然后把第2^i个元素的值加1

这样我们就可以得到元素出现次数上的规律了,设cnt[i]为前2^i个元素出现次数之和,那么cnt[i] = 2 * cnt[i-1] + 1

我们知道任何一个数都可以表示成若干个2的幂次方之和,那么给定一个下标n,我们便可以通过cnt求出a[n],具体做法如下:

    ll a = 0;
    for (int i = 62; i >= 0; i--) {
        while (cnt[i] <= n) {
            n -= cnt[i];
            a += (1LL<<i);
        }
    }

这里我们顺便求一下a[n]这个值在a序列中第一次出现的位置,原理同上,这个之后要用到

    ll pos = 0;
    for (int i = 62; i >= 0; i--) {
        while ((1LL<<i) <= a) {
            a -= (1LL<<i);
            pos += cnt[i];
        }
    }
    return pos + 1;//这里+1是因为规律是从第二项开始的

下面就可以求和了,再次观察每个数值的出现次数:

数值:        1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

出现次数:1 2 1 3 1 2 1 4 1  2   1   3   1   2   1   5...

我们发现1,3,5,7,9,11,13,15...出现了1次

                2,6,10,14...出现了2次

                4,12...出现了3次

打表打下去就会发现数值为奇数*1的数都出现了1次,数值为奇数*2的数都出现了两次,数值为奇数*3的数都出现了3次...

这就提醒我们枚举数值的出现次数i,i对应的数值序列实际上是一个等差数列,比如2对应的2,6,10,14...就是一个等差数列,这个等差数列的首项是2^(i-1),公差为2^i,那么我们只要知道等差数列的项数就可以求和了。

之前我们求得了a[n],那么我们便可以通过a[n]确定等差数列的项数。我们设等差数列的尾项为an,则有an<=a[n],这样我们便可以用等差数列的通项公式求出an,然后便可以知道有多少项了。

之后我们就可以对每个等差数列求和,然后乘上相应的出现次数i,理论上就可以得到结果了,但是还没完,因为等差数列中的尾项不一定出现i次,举个例子,n=10,那么可以求出a[10] = 6,出现2次的数值理论上有2,6,但是仔细看一下对于n=10,6这个数值是不是只出现了1次?这就提醒我们对于边界要特判,现在就可以考虑一下我之前为什么要求a[n]这个值在a序列中第一次出现的位置了,具体做法说起来比较乱,看代码吧,其实也不用非得像我这么做。

最后再次强调一下取模,能取模的地方尽量取!

到此就解决了这道题

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long ll;
const ll mod = 1e9 + 7;

ll cala(ll n) {//给定n,计算a[n]
    //因为规律是从第二项开始的,所以一开始先特判一下
    if (n == 1) return 1;
    n--;
    
    ll cnt[64];//cnt[i]表示数值1~2^i每项的出现次数之和
    cnt[0] = 1;
    for (int i = 1; i <= 62; i++) cnt[i] = 2*cnt[i-1] + 1;
    
    ll a = 0;
    for (int i = 62; i >= 0; i--) {
        while (cnt[i] <= n) {
            n -= cnt[i];
            a += (1LL<<i);
        }
    }
    return a;
}

ll posa(ll a) {//求a[n]这个值在a序列中第一次出现的位置
    //同样先特判一下
    if (a == 0) return 1;
    
    ll cnt[64];//cnt[i]表示数值1~2^i每项的出现次数之和
    cnt[0] = 1;
    for (int i = 1; i <= 62; i++) cnt[i] = 2*cnt[i-1] + 1;
    
    ll pos = 0;
    for (int i = 62; i >= 0; i--) {
        while ((1LL<<i) <= a) {
            a -= (1LL<<i);
            pos += cnt[i];
        }
    }
    return pos + 1;//这里+1是因为规律是从第二项开始的
}

int main() {
    int T;
    ll N;
    scanf("%d", &T);
    while (T--) {
        scanf("%lld", &N);
        ll a = cala(N);//求a[n]
        ll cnt = N - posa(a-1);//cnt为a[n]在序列中出现的次数
        ll ans = 0;
        for (int i = 1; (1LL<<(i-1)) <= a; i++) {
            //计算等差数列首项,公差,项数,尾项
            ll a1 = (1LL<<(i-1));
            ll d = (1LL<<i);
            ll n = ((a - a1) % d == 0 ? (a-a1)/d : (a-a1)/d+1);//注意一下这个判断
            ll an = a1 + d * (n-1);
            //求和,用了一下费马小定理
            ll sum = (((a1%mod + an%mod)%mod) * (n%mod)) % mod * 500000004 % mod;
            ans = (ans%mod + (sum%mod*i%mod)%mod) % mod;
            if ((a-a1) % d == 0) ans = (ans%mod + (cnt%mod*a%mod)%mod) % mod;//注意一下这个判断
        }
        printf("%lld\n", ans + 1);//规律是从第二项开始的,所以ans+1
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值