2018 多校联赛第一场1007:Chiaki Sequence Revisited(HDU 6304)

http://acm.hdu.edu.cn/showproblem.php?pid=6304

题意:给出一个数列的递推式,求前n项和。

(因为图片加载不上,递推式自己去HDU6304看吧)

思路:这个题的n非常大(预处理不现实,所以先找规律吧),并且查询的组数T<=1e5,(T非常大)所以一定是一个log级别的查询

我的思路跟题解可能不太一样,我下面写的是我比赛时的具体思考过程。

打表:

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int a[1050];
int main()
{
    a[1]=a[2]=1;
    for(int i=2;i<100;i++)
    {
        a[i]=a[i-a[i-1]]+a[i-1-a[i-2]];
        printf("%d\n",a[i]);
    }
}

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

看这个规律,你会发现所有的奇数都是出现了1次(除了1),那么先不管奇数,只看偶数

2 2 4 4 4 6 6 8 8 8 8 10 10 12 12 12 14 14 16 16 16 16 16 18 18 20 20 20 22 22 24 24 24 24 26 26 28 28 28 30 30 32 32 32 32 32 32 34 34 36 36 36 38 38 40 40 40 40

现在只看每个数字出现的个数(***数字出现的个数***

2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  6   (这些是到32的规律)

然后我猜想后面的规律为:2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  7

2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  6

2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  8

看到a[256]=128,并且128是出现了8次,完全跟我猜的次数一样。那么我就知道规律了

2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  6   

2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  7

2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  6

2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  8

将上面4行从中间分成两份,只有最后一个数字多了一个1,其他数字都一样

2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  6   

2  3  2  4  2  3  2  5  2  3  2  4  2  3  2  7

前两行也符合。

2  3  2  4  2  3  2  5                  2  3  2  4  2  3  2  6   

第一行也符合

2  3  2  4            2  3  2  5

一直这样分下去

2  3       2  4

2       3

还记得当时省略的那些 1 吗?

(因为最前面的那个1不符合规律,所以先将那个1忽略了,然后前几项如下所示):
1 2 2 3 4 4 4 5 6 6 7 8 8 8 8 9 10 10 11 12 12 12 13 14 14 15 16 16 16 16 16

---> 数字大小: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  3  2  4               2  3  2  5

现在我们把1加上, 1  2  1  3  1  2  1  4               1  2  1  3  1  2  1  5同样符合那个规律

1  2  1  3          1  2  1  4 (中间的空格代表分成了两部分,后一部分的最后一个数字,比前面的那个多1)

1  2         1  3  

1       2

这个规律很明显,这个只是第一步,找到了规律,现在需要将规律总结成公式

前几项和s[ i ] :  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16

---> 数字大小: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

s[1]=   1*1=1;
s[2]=s[1]+2*2=5;
s[3]=s[2]+3*1=8;
s[4]=s[3]+4*3=20;
这样推下去的话s数组肯定是开不下的,因为n<=1e18.所以考虑倍增思想。
我用s[0]表示前2^0项,s[i]表示前2^i项的总和。
那么我们就可以重新写出s数组的值了

s[0]=1;
s[1]=5;
s[2]=20;
s[3]=.....;
现在我们不能用数列来求s[ ]数组,应该利用规律,将s[i]=s[i-1]+  F( i ) ;
现在应该思考s[i]和s[i-1]有什么关系。

---> 数字大小:1  2  3  4  5  6  7  8

---> 出现次数:1  2  1  3  1  2  1  4

(我将s[2]简写成s2了)假设已知s2=1*1+2*2+3*1+4*3;

s3=s2+ 5*1+6*2+7*1+8*4 ;

1*1+2*2+3*1+4*3
5*1+6*2+7*1+8*4
观察黑体数字,这个规律再上面已经证明过了,只有最后一个数字多了1,其他都一样,那么我们最后再算那个多出来的数字

1*1+2*2+3*1+4*3
5*1+6*2+7*1+8*3       +8(这个8最后再算,先看前面这个式子怎么求)

用下面的这个式子减去上面这个式子得

4*1+4*2+4*1+4*3,是不是发现前面的那个差是一样的,(将差(4)提出来)

4*(1+2+1+3);

所以现在s3=s2+   (s2+4*(1+2+1+3)+8);这个式子很关键一定要看懂

---> 出现次数:1  2  1  3  1  2  1  4  1   2   1   3   1   2   1   5

这个数列还记得吧,因为求s3需要这个式子的前几项和,所以我定义a[i]为次数的前2^i项和(跟s数组的定义是一样的)

那么a0=1;
a1=3;
a2=7;
a3=15;
a数组是不是一眼就看出了规律。a[i]=2^(i+1) -1;

s3=s2+   (s2+4*(1+2+1+3)+8);

那么这个式子是不是可以化简成s3=2*s2 + 4*a2 + 8 ;
现在再思考4和8又是什么。4是差值,也就是两段数字的差值了,也就是前面一段的长度4,这个应该也很好理解
那8呢?是你所求的最后一个数字,并且这个数字一定是2的次幂,8=2^3

所以s3=2*s2+2^2*a2+2^3;

将这个式子转换成sn

及sn=2*s(n-1)+2^(n-1)*a(n-1)+2^n;(这些sn和an都是可以直接预处理出来的)(求到s61就够用了)

并且an=2^(n+1)-1;

到这里题目就已经解决了一半了。

现在我们自己已经总结出来的规律的公式。现在要想的是怎么将题目要求的前n项转化为我们已知的sn和an。(想办法往公式上套就行了)

重点来了:现在求n=100的答案;

我们的sn表示的都是规律的前2^n项,那么我们要把100转化成2的次幂的形式

---> 数字大小: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

现在再看这个数列的规律,我们先理一下思路,因为这里有个比较绕的东西。

首先前n=100项的和是不是包含第一项1,我们的规律是不包含最前面的1的,所以1最后单独处理

所以也就是求我们规律的前99项,对吧
现在理清楚前99项对于规律到底是个啥东西。

首先a数组表示的是数字的个数,a0=1表示规律的前一项。a1=3表示规律的前五项,a2=7表示规律的前七项,
an=2^(n+1)-1;
sn对应的是an,

所以将99对应an=2^(n+1)-1;才能对应sn(an对应的所有项的答案就是sn)

所以要将99转化为2的幂次-1,才行。

99=63+31+3+1+1,转化完之后再分别求即可。99相当于分成了5段,第一段长度为63,第二段长度为31,以此类推

第一段长度为63怎么求,

a5=2^(5+1) -1=63; 所以63对应的是a5,对应的答案的贡献是s5,所以前63项就求出来了,就是s5.

接着求第二段长度为31,(这里是难点)这个求法跟前面推sn的递推式是一样的。

这段的31个数字跟最前面的31个数字出现的次数是一一对应相等的,只是数字大小不同,而且数字大小差值,是能求出来的是32

做差为32*(a5)

为了能讲清楚,我把第一段前63项打出来:1 2 2 3 4 4 4 5 6 6 7 8 8 8 8 9 10 10 11 12 12 12 13 14 14 15 16 16 16 16 16 17 18 18 19 20 20 20 21 22 22 23 24 24 24 24 25 26 26 27 28 28 28 29 30 30 31 32 32 32 32 32 32

第二段长度31:33 34 34 35 36 36 36 37 38 38 39 40 40 40 40 41 42 42 43 44 44 44 45 46 46 47 48 48 48 48 48

请注意:1 2 2 3 4 4 4 5 6 6 7 8 8 8 8 9 10 10 11 12 12 12 13 14 14 15 16 16 16 16 16的和是不是s4(s4表示前a4=2^5-1项的和)

第二段每个数字比s4表示的数字都多了32,那么第二段的和就等于s4+32*a4.比s4总共多了a4个32;

这点比较难理解,剩下的就好办了(看不懂这里的再回去看一下我是怎么推出sn的,方法是一样的,利用的也是差值)

剩下的第三段跟第二段计算方法一样,第三段的和= s1+(32+16)*a1;

第四段的和= s0+(32+16+2)*a0;

第五段的和= s0+(32+16+2+1)*a0;

所以前100项的和就等于s5+s4+32*a4+s1+(32+16)*a1+s0+(32+16+2)*a0+s0+(32+16+2+1)*a0;

附上比赛时的AC代码:

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
LL s[105],a[105],fac[105];//fac[i]=2^i,s和a数组跟博客里讲的一样
void init()//预处理
{
    fac[0]=1;
    s[0]=1;
    a[0]=1;
    for(LL i=1;i<=61;i++)fac[i]=fac[i-1]*2;
    for(LL i=1;i<=61;i++)a[i]=(fac[i+1]-1+mod)%mod;
    for(LL i=1;i<=61;i++)s[i]=(s[i-1]*2%mod+(fac[i-1]%mod)*a[i-1]%mod+fac[i])%mod;
}
LL query(LL x)
{
    LL y=x,ans=0,t=0;//t来存储差值
    for(LL i=61;i>=1;i--)
    {
        while(y>=fac[i]-1)
        {
            ans=(ans+s[i-1]+t*(a[i-1]))%mod;
            t=(t+fac[i-1])%mod;//更新差值
            y-=fac[i]-1;
        }
    }
    return (ans+1)%mod;//+1是加上第一项的1
}
int main()
{
    init();
    LL t,n;
    scanf("%lld",&t);
    while(t--)
    {
        scanf("%lld",&n);
        printf("%lld\n",query(n-1));//我们推得规律是不包含第一项的,所以n先-1。最后加上即可
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xiangaccepted/article/details/81215737