数值统计题解

本文介绍了一种使用动态规划和二叉树结构解决数列统计问题的方法,通过构造滚动数组和二叉树,高效计算数列前N项中等于特定值K的项数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目描述】

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]+1n为奇数),我们发现,这个数列可以用一棵二叉树来表示各个元素相互之间的关系。

          这样,我们可以利用动规,求出每一层每个数出现的次数。

         令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

转载请注明出处

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值