《算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。
【题目描述】 f(i)表示数字i中二进制1的数量,求f(1)f(2)…*f(n):
【输入格式】 输入一个正整数n,n不超过10^15。
**【输出格式】**输出一个数字表示答案,对10000007取模。
【输入样例】
10
【输出样例】
96
n最大是 1 0 15 10^{15} 1015,如果直接求f(1)、f(2)、…、f(n),超时。
如何求f(1)×f(2)×…×f(n)?可以利用二进制的特征,找规律。
在1~n这n个数的二进制表示中,包含1个“1”的数有1、10、100、1000、…,设共有a个;
包含2个“1”的数有11、101、110、1001、…,设共有b个;
包含3个“1”的数有111、1011、1101、…等等。
那么 f ( 1 ) × f ( 2 ) × . . . × f ( n ) = 1 a × 2 b × 3 c × 4 d × . . . f(1)×f(2)×...×f(n) = 1^a×2^b×3^c×4^d×... f(1)×f(2)×...×f(n)=1a×2b×3c×4d×...,只要求出a、b、c、d、…即可算出答案。
如何求a、b、c、d、…?例如n = 1 0 15 10^{15} 1015时,在1~n这n个数中,包含1个“1”的数有a = 50个,含有2个“1”的数有b = 1225个;… 含有3个“1”的数有c = 19600个;…等等。
本题是一道典型的数位统计DP题目:计算1~n中包含k个“1”的数有多少个。k = 1, 2, 3, … len,这里len是数的二进制表示的最大长度,例如22,二进制10110,len = 5。
注:下面的题解可能还是有些晦涩,需要先学习数位统计DP的原理和两种编码方法,参考《算法竞赛》,清华大学出版社,罗勇军、郭卫斌著,333~338页。
定义状态dp[pos][sum],表示当前二进制数长度为pos位,前面1的个数为sum个时,含有k个“1”的数字个数。
例:
(1)k=1。dp[1][0]=1,二进制数长度为1,即数字0和1,且前面没有“1”,那么含有k=1个“1”的数有1个,就是1。dp[2][1]=1,二进制数长度为2,数字范围00-11,前面有1个“1”,那么00-11这4个数只能取00,只有1个。dp[5] [0] = 5,在数字00000-11111中,可以取1、10、100、1000、10000,共5个。
(2)k=2。dp[3][0]= 3,二进制数长度为0,数字范围000-111,前面没有“1”,那么含有k=2个“1”的数有3个,即011、101、110。dp[4] [1] = 4,二进制数长度为4,数字范围0000-1111,前面有一个“1”,那么0000-1111中只能取有1个“1”的情况,即0001、0010、0100、1000。
数位统计DP有两种代码实现:递推实现、记忆化搜索。记忆化搜索的逻辑更清晰一些。
函数dfs(int pos, int sum, bool limit, int k):计算dp[pos][num]。
返回值:有k个“1”的数有多少个。
pos:当前处理到第pos位数;
sum:前面有sum个1;
limit:当前最高pos位有没有限制。false表示没有限制,这一位等于0或1都行;true表示有限制,只能是1,或者只能是0。
k:这个二进制数字中只能有k个1。
以n = 22为例,n的二进制10110,长度len=5,下面介绍如何计算k=1个“1”的数有多少个。
第一次,dfs(len, 0, true,1):当前处理到第pos = len = 5位,即最高位;sum = 0,前面没有出现过1;limit = true,表示最高位(len=5这一位)有限制,只能是1;k=1,这个二进制数中只能有1个“1”。
dfs(len, 0, true,1)的返回值:返回1~10110范围内,只有k = 1个“1”的数有多少个。答案是5个,即1、10、100、1000、10000。
继续dfs时,有两种情况:
(1)若最高位pos=1,说明出现了一个“1”,下一步这样继续dfs:
dp[pos][sum] += dfs(pos-1, sum+1, limit&&(i==up), k);
pos-1,继续处理下一位;sum+1,前面出现了1个“1”;limit&&(i==up),当前最高位有限制且是1。
(2)若最高位pos=0,说明前面没有没有出现“1”,下一步这样继续dfs:
dp[pos][sum] += dfs(pos-1, sum, limit&&(i==up), k);
pos-1,继续处理下一位;sum,前面没有出现“1”,sum不变;limit&&(i==up),当前最高位有限制且是0。
用DP计算出a、b、c、…后,再计算 f ( 1 ) × f ( 2 ) × . . . × f ( n ) = 1 a × 2 b × 3 c × . . . f(1)×f(2)×...×f(n) = 1^a×2^b×3^c×... f(1)×f(2)×...×f(n)=1a×2b×3c×...。由于a、b、c、…可能很大,需要用快速幂计算。例如 n = 1 0 15 ≈ 2 50 n = 10^{15} ≈ 2^{50} n=1015≈250时, f ( 1 ) × f ( 2 ) × . . . × f ( n ) = 1 a × 2 b × 3 c × . . . × 4 9 y × 5 0 z , a + b + c + . . . + y + z ≈ 2 50 f(1)×f(2)×...×f(n) = 1^a×2^b×3^c×...×49^y×50^z,a+b+c+... +y+z ≈ 2^{50} f(1)×f(2)×...×f(n)=1a×2b×3c×...×49y×50z,a+b+c+...+y+z≈250。
【重点】 数位统计DP。
#include
using namespace std;
typedef long long ll;
ll dp[60][60];
int num[60];
ll MOD = 1e7+7;
ll fastpow(ll a, ll n){ //快速幂
ll ans = 1;
a %= MOD;
while(n){
if (n&1) ans = ans*a % MOD;
a= a*a % MOD;
n >>= 1;
}
return ans;
}
ll dfs(int pos, int sum, bool limit, int k){ //返回值:数字中有k个1的数是多少个?
if (pos == 0) //递归到0位数,即整个数处理完了
return sum == k; //如果正好有sum=k个1,说明这个数字符合要求,返回1
if (!limit && dp[pos][sum]!=-1) //当前最高位没有限制,并且以前计算过,直接返回
return dp[pos][sum]; //记忆化搜索
int up; //up:当前最高位的最大值
if (limit) up = num[pos]; //当前最高位有限制,就是num[pos]
else up = 1; //当前最高位没有限制,那么最大值是1
dp[pos][sum] = 0; //准备计算dp[pos][sum]
for (int i=0 ; i<=up ; i++){
if(i==1) dp[pos][sum] += dfs(pos-1, sum+1, limit&&(i==up), k);
else dp[pos][sum] += dfs(pos-1, sum, limit&&(i==up), k);
}
return dp[pos][sum];
}
int main(){
ll n; cin >> n;
int len = 0;
while(n){ //对n进行二进制分解,存在num[]中。
len++; //二进制的位数
num[len] = n%2;
n >>= 1;
}
ll ans = 1;
for (int i=1 ; i<=len; i++){
memset(dp, -1, sizeof(dp));
ll cnt = dfs(len, 0, true, i); //cnt:1~n中包含i个1的数有多少个
ans = ans*fastpow(i, cnt)%MOD;
}
cout << ans;
return 0;
}
import java.util.Scanner;
public class Main {
static long[][] dp = new long[60][60];
static int[] num = new int[60];
static long MOD = 10000000L + 7L;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long n = sc.nextLong();
int len = 0;
while (n != 0) {
len++;
num[len] = (int) (n % 2);
n >>= 1;
}
long ans = 1L;
for (int i = 1; i <= len; i++) {
for (int j = 0; j <= len; j++) //初始化
for (int k = 0; k <= len; k++)
dp[j][k] = -1L;
long temp = dfs(len, 0, true, i);
ans = ans * fastpow(i, temp) % MOD;
}
System.out.println(ans);
sc.close();
}
static long fastpow(long a, long n) {
long ans = 1L;
a %= MOD;
while (n != 0) {
if ((n & 1) == 1) ans = ans * a % MOD;
a = a * a % MOD;
n >>= 1;
}
return ans;
}
static long dfs(int pos, int sum, boolean limit, int k) {
if (pos <= 0) return (sum == k) ? 1L : 0L;
if (!limit && dp[pos][sum] != -1) return dp[pos][sum];
int up = -1;
if (limit) up = num[pos];
else up = 1;
dp[pos][sum] = 0L;
for (int i = 0; i <= up; i++) {
if (i == 1) dp[pos][sum] += dfs(pos - 1, sum + 1, limit && (i == up), k);
else dp[pos][sum] += dfs(pos - 1, sum, limit && (i == up), k);
}
return dp[pos][sum];
}
}
dp = [[-1 for _ in range(60)] for _ in range(60)]
num = [0] * 60
MOD = 10000000 + 7
def fastpow(a, n):
ans = 1
a %= MOD
while n:
if n & 1: ans = ans * a % MOD
a = a * a % MOD
n >>= 1
return ans
def dfs(pos, sum, limit, k):
if pos <= 0: return int(sum == k)
if not limit and dp[pos][sum] != -1: return dp[pos][sum]
up = -1
if limit: up = num[pos]
else: up = 1
dp[pos][sum] = 0
for i in range(up + 1):
if i == 1: dp[pos][sum] += dfs(pos - 1, sum + 1, limit and (i == up), k)
else: dp[pos][sum] += dfs(pos - 1, sum, limit and (i == up), k)
return dp[pos][sum]
n = int(input())
len = 0
while n:
len += 1
num[len] = n % 2
n >>= 1
ans = 1
for i in range(1, len + 1):
for j in range(0, len + 1):
for k in range(0, len + 1):
dp[j][k] = -1
cnt = dfs(len, 0, True, i)
ans = ans * fastpow(i, cnt) % MOD
print(ans)