【题目描述】
Joy是一个数学系的学生。老师给了她一个数列,数列中的每一个数都是整数。她的任务是统计数列中前N项的取值。由于老师给出的N非常大,使得Joy根本无法自己去数。这就让她束手无策了。
在万般无奈时,她突然想起住在隔壁的你是程序设计方面的高手,所以她马上跑来向你求助。为了不使她失望,你一口就答应了她的请求。她告诉了你这个数列的递推公式,她希望你能编一个程序:对于任意的N和K,能够快速地统计出该数列的前N项中值等于K的项的个数。
这个数列是:a1=0 a2=1
a2n+1=an+1-2 a2n+2=an+1 +1 (n ∈ Z+)
输入输出:输人文件Sta.in包含两行。第一行是一个整数N(1≤N≤1000000000),表示需要统计的项数。第二行包含一个整数K(|K|≤10000)。
输出文件Sta.out中应仅有一个数,表示数列{a}的前N项中值等于K的项数。
样例:Sta.In Sta.out
6 2
0
样例说明:
a1=0, a2=1,a3=-1,a4=2,a5=-3,a6=0,
所以数列{ai}的前6项中有2项为0。
【题解】
首先,我打了一个小表:
0, 1, -1,2, -3,0,0,3, -5,-2,-2,1,-2,1,1,4, -7,-4,-4,-1,-4,-1,-1,2,-4,-1,-1,2,-1,2,2,5……
观察这个表,可以发现所有的数与a[1]都没有关系。所以,我们去掉a[1],得到下面的东西:
1, -1,2, -3,0,0,3, -5,-2,-2,1,-2,1,1,4, -7,-4,-4,-1,-4,-1,-1,2,-4,-1,-1,2,-1,2,2,5……
这样,数列的通项公式变成了:a[2n]=a[n]-2 a[2n+1]=a[n]+1
是否似曾相识呢?如果把它改成a[n]=a[n>>1]-2 (n为偶数)a[n]=a[n>>1]+1(n为奇数),我们发现,这个数列可以用一棵二叉树来表示各个元素相互之间的关系。
这样,我们可以利用动规,求出每一层每个数出现的次数。
令f[i,j]表示第i层值为j的数出现的次数,那么有f[i,j]=f[i-1,j+2]+f[i-1,j-1]。由于层数为log(n),数的范围也是log(n)级别的,所以它的复杂度是(log(n))^2的。这个过程可以用滚动数组实现。
但是,我们只能求出整层的f值,而不知道他们的具体顺序,所以最后一层我们还要分开考虑。
这个问题我是利用一种类似线段树的方法实现的,复杂度也是(log(n))^2,具体的实现方法请直接看代码。
Code(这是考场上的代码,写得略丑了):
program sta;
type
int=longint;
list=array[-100..100]of int;
var
i,j,k,s,m,n,ans:int;
a,b:list;
qu:array[0..700000]of int;
function log(x:int):int;
var i:int;
begin
i:=0;
while x<>0 do begin
inc(i);x:=x>>1;
end;
exit(i);
end;
procedure dp(fir,n:int);
begin
m:=1;s:=2;
fillchar(a,sizeof(a),0);
a[fir]:=1;
inc(ans,a[k]);
while(m<n)do begin
for i:=-99 to 98 do b[i]:=a[i+2]+a[i-1];
inc(ans,b[k]);
a:=b;
m:=m+1;s:=s<<1;
end;
end;
function f(l,r:int;a:list):int;
var i,j,s:int;
begin
if l=r then begin
s:=1;
while l<>1 do begin
if l and 1=1 then s:=s+1
else s:=s-2;
l:=l>>1;
end;
exit(a[s]);
end;
if(l=r-1)and(l and 1=1)then begin
exit(f(r,r,a)+f(l,l,a));
end;
f:=0;
if l and 1=1 then begin inc(f,f(l,l,a));inc(l);end;
if r and 1=0 then begin inc(f,f(r,r,a));dec(r);end;
for i:=-98 to 99 do b[i]:=a[i-2]+a[i+1];
a:=b;
inc(f,f(l>>1,r>>1,a));
end;
begin
assign(input,'sta.in');reset(input);
assign(output,'sta.out');rewrite(output);
read(n,k);
ans:=ord(k=0);
if(k>=(-2*log(n)-2))and(k<=(log(n)+1))then begin
dp(1,log(n)-1);
fillchar(a,sizeof(a),0);
a[k]:=1;
if ( (1<<m) <=n-1) then inc(ans,f(1<<m,n-1,a));
end else ans:=0;
write(ans);
close(input);close(output);
end.
其中,dp是dp的过程,a,b是我用的滚动数组。f函数中的a[i]表示的是在当前的区间内,一个值为i的数能在最后一层产生多少值为k的数。分析这个函数的复杂度可以参照线段树的复杂度分析方法。
这道题目高一的似乎还有一种深搜的方法·,有兴趣的可以自己研究一下(据DRJ说这种方法的本质和我的是一样的)。
BY QW
转载请注明出处