bzoj 3209 数论/数位DP

题意:定义sum(i)表示数i的二进制表示中1的个数,给定一个n,求phi(sum(i)) (1<=i<=n) 对10000007 取模的结果

一眼看过去,暴力打表找规律,然后并没有发现任何有用的事情(然而机房里的一个神犇用神一样的高端大气上档次的达标方法成功找到规律并直接拿下first blood   orz)

然后就在纸上手动模拟,只要找到1~n中,二进制表示恰好有k个1的数的个数再乘上这么多的k即可

显然用快速幂,个数作为指数,k作为底数

怎样统计呢?

组合数嘛~

c(n,m)表示n位二进制中有m个1

那么显然 c(n,m)=c(n-1,m)+c(n-1,m-1)

然后对于给定的n,转化成二进制后有l位,

枚举1的个数x(1<=x<=l)

对于每个x,从最高位到最低位判断n的二进制数的该位是否是1

如果是1,则inc(ans,c[i-1,x])并同时dec(x),然后继续

注意:(1)因为这种方法只能求出小于n的phi,所以在处理之前inc(n),这样处理出的答案就是1~n的

             (2)组合数作为指数,不需要取模 2333333

             (3)组合数的初值,从0~n的c[i,0]=1,c[1,1]=1,mdzz,就因为这里一直wa

             (4)注意没找到一个该位是1的要dec(x),不然会算到比n大的数

const
        mo=10000007;
var
        n,ans,l,tt      :int64;
        way             :array[0..70] of int64;
        i               :longint;
        c               :array[0..70,0..70] of int64;
procedure pre_do;
var
        i,j :longint;
begin
   for i:=0 to 60 do c[i,0]:=1;
   c[1,1]:=1;
   for i:=2 to 60 do
     for j:=1 to i do
       c[i,j]:=c[i-1,j-1]+c[i-1,j];
end;

function work(x:longint):int64;
var
        ans:int64;
        i:longint;
begin
   ans:=0;
   for i:=l downto 1 do
     if way[i]=1 then
     begin
        inc(ans,c[i-1,x]);
        dec(x);
        if x<0 then break;
     end;
   exit(ans);
end;

function qp(a,b:int64):int64;
var
        tt:longint;
begin
   tt:=1;
   while (b>0) do
   begin
      if b and 1=1 then tt:=tt*a mod mo;
      a:=a*a mod mo;
      b:=b>>1;
   end;
   exit(tt);
end;

begin
   read(n);
   if n=0 then
   begin
      writeln(0);exit;
   end;
   pre_do;
   l:=0; inc(n);
   while (n>0) do
   begin
      inc(l);
      way[l]:=n and 1;
      n:=n>>1;
   end;
   ans:=1;
   for i:=1 to l do
     ans:=ans*qp(i,work(i)) mod mo;
   writeln(ans);
end.
——by Eirlys

bzoj 3209 数论/数位DP_第1张图片

你可能感兴趣的:(bzoj,DP,排列组合)