2018 多校聯賽第一場1007:Chiaki Sequence Revisited(HDU 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;
}