【数位DP】【P4317】花神的数论题

【数位DP】【P4317】花神的数论题

Description

给定 \(n\),求 \(n\) 以内所有正整数二进制下 \(1\) 的个数的乘积,答案对 \(10^7 + 7\) 取模

Limitations

\(1 \leq n \leq 10^{15}\)

Solution

这是我见过最简单的数位 DP,然而我还是翻车了……

考虑 \(1\) 最多有 \(O(\log n)\) 个,因此 \(1\) 的个数是可以枚举的。

枚举求有 \(k\)\(1\) 的数的个数,设 \(f_{i, j}\) 是考虑前 \(i\) 位,剩下的位置还需要填 \(j\)\(1\) 且不顶上界的方案数。转移显然:

\[f_{i,j} = f_{i-1,j+1} f_{i-1,j} \]

分别对应第 \(i\) 位填 \(0\) 和填 \(1\)

顶上界的情况记一下已经有几个 \(1\) 就可以更新了。

因为只考虑 \(1\) 的个数,所以前导 \(0\) 对答案没有贡献,不需要做特殊处理。

需要注意的是,\(10^7 + 7\) 并不是一个质数,\(10^7 + 7 ~=~941 \times 10627\),因此在 DP 的时候并不能模 \(p - 1\),因为他的欧拉函数并不是减一简单。事实上并不需要取模,因为方案数不会超过 \(n\),因此直接计算即可。

Code

#include <cstdio>
#include <cstring>

const int maxn = 64;
const int MOD = 10000007;

ll n;
int cnt, upc = -1;
ll ans = 1;
ll frog[maxn][maxn];
int MU[maxn];

ll calc(const int x);
ll mpow(const int x, ll y);

int main() {
  freopen("1.in", "r", stdin);
  qr(n);
  do cnt += (MU[++upc] = n & 1); while (n >>= 1);
  for (int i = 1; i < 60; ++i) {
    ans = ans * mpow(i, calc(i)) % MOD;
  }
  ans = ans * cnt % MOD;
  qw(ans, '\n', true);
  return 0;
}

ll calc(const int x) {
  int cnt = x - 1;
  memset(frog, 0, sizeof frog);
  frog[upc][x] = 1;
  for (int i = upc - 1; ~i; --i) {
    int di = i + 1;
    for (int j = 0; j <= x; ++j) {
      frog[i][j] = frog[di][j + 1] + frog[di][j];
    }
    if (MU[i] && ~cnt) {
      ++frog[i][cnt--];
    }
  }
  return frog[0][0];
}

ll mpow(const int x, ll y) {
  ll _ret = 1, _tmp = x;
  while (y) {
    if (y & 1) {
      _ret = _ret * _tmp % MOD;
    }
    _tmp = _tmp * _tmp % MOD;
    y >>= 1;
  }
  return _ret;
}

Summary

神tm \(10^7+7\) 竟然不是个质数

posted @ 2019-08-31 02:02  一扶苏一  阅读(166)  评论(0编辑  收藏  举报