花神的数论题

花 神 的 数 论 题 花神的数论题

题目链接: l u o g u   P 4317 luogu\ P4317 luogu P4317

题目背景

众所周知,花神多年来凭借无边的神力狂虐各大 O J OJ OJ O I OI OI C F CF CF T C TC TC … … …… 当然也包括 C H CH CH 啦。

题目

话说花神这天又来讲课了。课后照例有超级难的神题啦…… 我等蒟蒻又遭殃了。 花神的题目是这样的:设 sum ( i ) \text{sum}(i) sum(i) 表示 i i i 的二进制表示中 1 1 1 的个数。给出一个正整数 N N N ,花神要问你 ∏ i = 1 N sum ( i ) \prod_{i=1}^{N}\text{sum}(i) i=1Nsum(i),也就是 sum ( 1 ) ∼ sum ( N ) \text{sum}(1)\sim\text{sum}(N) sum(1)sum(N) 的乘积。

输入

一个正整数 N N N

输出

一个数,答案模 10000007 10000007 10000007 的值。

样例输入

3

样例输出

2

数据范围

对于 100 % 100\% 100% 的数据, N ≤ 1 0 15 N≤10^{15} N1015

思路

这道题是数位 d p dp dp,但是我用的是组合数。

我们从大到小枚举位数,找到是 1 1 1的位数,那我们就可以让它为 0 0 0,后面的位数就可以随便枚举了。接着枚举玩这一位之后,我们只要让它一直是 1 1 1,那么我们就可以继续枚举下面的,而不会重复也不会超上限来。

除了枚举位数(设用 i i i枚举),还要枚举未确定的位数中 1 1 1的个数(设用 j j j枚举),那么我们设之前以及确定来的是 1 1 1的位数的数量是 t t t,那么答案要乘上的值就是 ( t + j ) C i j (t+j)^{C^j_i} (t+j)Cij ( t + j ) (t+j) (t+j)就是整个数列中 1 1 1的个数,而 C i j C^j_i Cij就不用说了,组合数嘛,求出 1 1 1的个数为 ( t + j ) (t+j) (t+j)的数有多少个嘛。
(至于 C i j C^j_i Cij怎么搞,肯定就是预处理啊,因为 n n n化为二进制肯定不超过 60 60 60位,那我们只需要预处理出 60 60 60内的就可以了)

最后输出的时候,有一点要注意,就是还要乘上计数器 t t t。因为按上面那样的话是不会算到 n n n本身的情况的,所以我们输出的时候还要再乘上 t t t才可以。

还有一个很重要的东西,因为 10000007 10000007 10000007它不是素数,所以我们预处理组合数的时候就不可以用 10000007 10000007 10000007,而是要用它的欧拉函数值。
φ ( 10000007 ) = 9988440 \varphi(10000007)=9988440 φ(10000007)=9988440

代码

#include<cstdio>
#define mo 10000007
#define ll long long

using namespace std;

ll n, C[61][61], t, ans = 1;

ll qsm(ll a, ll b) {//快速幂
	ll anan = 1;
	while (b) {
		if (b & 1) anan = (anan * a) % mo;
		a = (a * a) % mo;
		b >>= 1;
	}
	return anan;
}

int main() {
	for (int i = 0; i <= 60; i++)//预处理组合数
		C[i][0] = 1;
	for (int i = 1; i <= 60; i++)
		for (int j = 1; j <= i; j++)
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % 9988440;//因为10000007不是素数,所以要用它的欧拉函数值phi(10000007)=9988440
	
	scanf("%lld", &n);//读入
	
	for (int i = 60; i >= 0; i--) {//枚举没确定位数
		if (!((n >> i) & 1)) continue;//当前位是0
		for (int j = 0; j <= i; j++)//枚举没确定的1的个数
			if (t + j)//的出来的至少有一个1
				ans = (ans * qsm(t + j, C[i][j])) % mo;//乘起来
		t++;//加计数器
	}
	
	printf("%lld", ans * t % mo);//输出(要记得乘上计数器)
	
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值