【BZOJ3209】花神的数论题(数位DP)

点此看题面

大致题意: s u m ( i ) sum(i) sum(i)表示 i i i二进制中1的个数,请求出 ∏ i = 1 n s u m ( i ) \prod_{i=1}^n sum(i) i=1nsum(i)


数位 D P DP DP

很显然,这是一道数位 D P DP DP题。我们可以先将 n n n转化为二进制,然后DP预处理,最后求答案。

f [ i ] [ j ] f[i][j] f[i][j]表示当前数字的1~ i i i位中共有 j j j个1,这可以得到转移方程:

f[i][j]=f[i-1][j]+f[i-1][j-1];

初始时将全部 f [ i ] [ 0 ] f[i][0] f[i][0]赋值为1。

然后我们就能发现,这样子我们就相当于求出了一个杨辉三角形

最后,再对 s u m ( i ) sum(i) sum(i)的每一种可能值依次进行操作,求出有多少个数在二进制下有 i i i个1,再用快速幂将其累乘即可求出答案。


代码
#include<bits/stdc++.h>
#define LL long long
#define YKH 10000007
using namespace std;
LL n,ans=1ll,tot,num[100],f[100][100];
inline char tc()
{
    static char ff[100000],*A=ff,*B=ff;
    return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(LL &x)
{
    x=0;LL f=1;char ch;
    while(!isdigit(ch=tc())) f=ch^'-'?1:-1;
    while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
    x*=f;
}
inline void write(LL x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline LL quick_pow(LL x,LL y)//快速幂
{
    LL res=1;
    while(y)
    {
        if(y&1) (res*=x)%=YKH;
        (x*=x)%=YKH,y>>=1;
    }
    return res;
}
inline LL doing(LL x)//求出二进制下含有i个1的数的个数,利用了先前求出的杨辉三角形
{
	LL sum=0;//统计个数
	for(register LL i=tot;i>0;--i)
	{
		if(num[i]) sum+=f[i-1][x--];//判断该位是否为1
		if(x<0) return sum;//如果x小于0,返回sum
	}
	return sum;
}
int main()
{
    register LL i,j;LL w;
    for(read(n),w=n+1,tot=0;w;num[++tot]=w&1,w>>=1);
    for(i=0;i<=tot;++i) f[i][0]=1;
    for(i=1;i<=tot;++i)//预处理出一个杨辉三角形
    	for(j=1;j<=i;++j)
    		f[i][j]=f[i-1][j]+f[i-1][j-1];
    for(i=1;i<=tot;++i)
        (ans*=quick_pow(i,doing(i)))%=YKH;//求出答案,并累乘
    return write(ans),0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值