• 【数位dp】bzoj3209: 花神的数论题


    Description

    背景
    众所周知,花神多年来凭借无边的神力狂虐各大 OJ、OI、CF、TC …… 当然也包括 CH 啦。
    描述
    话说花神这天又来讲课了。课后照例有超级难的神题啦…… 我等蒟蒻又遭殃了。
    花神的题目是这样的
    设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问你
    派(Sum(i)),也就是 sum(1)—sum(N) 的乘积。

    Input

    一个正整数 N。

    Output

    一个数,答案模 10000007 的值。

    Sample Input

    样例输入一
    3

    Sample Output

    样例输出一

    2

    HINT

    对于样例一,1*1*2=2;

    数据范围与约定

    对于 100% 的数据,N≤10^15


    题目分析

    是一道入门的数位dp(组合数)题。

    但是这题结合了很多出题人的恶意,并且具有一定的启示作用。

     1 #include<bits/stdc++.h>
     2 const int MO = 10000007;
     3 
     4 int ans,pre;
     5 int f[103][103];
     6 int digit[23];
     7 long long n;
     8 
     9 int qmi(int a, int b)
    10 {
    11     int ret = 1;
    12     while (b)
    13     {
    14         if (b&1) ret = 1ll*ret*a%MO;
    15         a = 1ll*a*a%MO;
    16         b >>= 1;
    17     }
    18     return ret;
    19 }
    20 int main()
    21 {
    22     scanf("%lld",&n);
    23     f[0][0] = 1, ans = 1;
    24     for (n++; n; n>>=1) digit[++digit[0]] = n&1;
    25     for (int i=1; i<=digit[0]; i++)
    26     {
    27         f[i][0] = 1;
    28         for (int j=1; j<=i; j++)
    29             f[i][j] = f[i-1][j]+f[i-1][j-1];    //预处理组合数
    30     }
    31     for (int i=digit[0]; i; i--)
    32         if (digit[i]){
    33             for (int j=i-1; j>=1; j--)
    34                 ans = 1ll*ans*qmi(pre+j, f[i-1][j])%MO;
    35             pre++;
    36         }
    37     printf("%d
    ",ans);
    38     return 0;
    39 }

    最初会自然地想到上面这种dp方法。

    但是!这里的细节显然崩坏了。

    二进制拆分

    1 int digit[23];

    二进制拆分时候$digit[]$干嘛开这么小啊……注意要大概开个三倍。

    组合数取模

    1     for (int i=1; i<=digit[0]; i++)
    2     {
    3         f[i][0] = 1;
    4         for (int j=1; j<=i; j++)
    5             f[i][j] = f[i-1][j]+f[i-1][j-1];
    6     }

    这里组合数不取模显然是会溢出的。但是,重点是取什么模呢?可能会不假思索地%1e7+7,然而实际上1e7+7并不是一个素数,所以这里要回到欧拉定理,我们有$φ(1e7+7)=9988440$。再者就是注意这里只有组合数需要对$φ(1e7+7)$取模。

    溢出会RE;或是莫名其妙TLE。

    dp的转移

    1     for (int i=digit[0]; i; i--)
    2         if (digit[i]){
    3             for (int j=i-1; j>=1; j--)
    4                 ans = 1ll*ans*qmi(pre+j, f[i-1][j])%MO;
    5             pre++;
    6         }

    注意到这里内层的$j>=1$,但是由于后面的元素可以不选,事实上$j$应该$>=0$才对。

    手调时候会发现当$j=0,pre=0$时,$ans$就等于0了。

    所以还要在快速幂里特判一层: if (!a) return 1; 。

    正确代码

     1 #include<bits/stdc++.h>
     2 const int MO = 10000007;
     3 
     4 int ans,pre;
     5 int f[103][103];
     6 int digit[53];
     7 long long n;
     8 
     9 int qmi(int a, int b)
    10 {
    11     if (!a) return 1;
    12     int ret = 1;
    13     while (b)
    14     {
    15         if (b&1) ret = 1ll*ret*a%MO;
    16         a = 1ll*a*a%MO;
    17         b >>= 1;
    18     }
    19     return ret;
    20 }
    21 int main()
    22 {
    23     scanf("%lld",&n);
    24     f[0][0] = 1, ans = 1;
    25     for (n++; n; n>>=1) digit[++digit[0]] = n&1;
    26     for (int i=1; i<=digit[0]; i++)
    27     {
    28         f[i][0] = 1;
    29         for (int j=1; j<=i; j++)
    30             f[i][j] = (f[i-1][j]+f[i-1][j-1])%9988440;
    31     }
    32     for (int i=digit[0]; i; i--)
    33         if (digit[i]){
    34             for (int j=i-1; j>=0; j--)
    35                 ans = 1ll*ans*qmi(pre+j, f[i-1][j])%MO;
    36             pre++;
    37         }
    38     printf("%d
    ",ans);
    39     return 0;
    40 }

    END

  • 相关阅读:
    python 发邮件乱码
    膳魔师杯使用注意事项
    了解指针,分分钟的事情 C++筆記--指針
    海淘攻略
    【转】Cocos2dx.3x入门三部曲
    在Windows7上搭建Cocos2d-x 3.2alpha0开发环境
    黑苹果 MAC OS X 10.10.2 安装流程
    Linux 下如何查找木马并处理
    js如何判断访问是来自搜索引擎(蜘蛛人)还是直接访问
    泰*网 Centos 一些命令
  • 原文地址:https://www.cnblogs.com/antiquality/p/9343276.html
Copyright © 2020-2023  润新知