题解 luogu.P4317 花神的数论题

题目

luogu.P4317 花神的数论题

不怎么裸的数位DP题目。而且暴力过半,还是不错的。

题意建模

首先,暴力肯定很好想。 \(O(log_{2}N)\) 的时间复杂度预处理每一个数的二进制数中1的个数,然后 \(O(n)\) 依次检查每个数,可以通过 \(1/2\) 的数据。其实瓶颈在空间上。不然一次开不下 \(1e9\) 的数组,可以多开几个,这样再吸口氧勉勉强强是可以混过去的。

下面我们考虑一下正解。

算法分析

考虑将区间内的数压成二进制,然后逐位检查。经估算,\(50>1e15\) 所以,这里基本上就是一个常数级别的复杂度。

更直接地,我们要做的事情就是:统计一个十进制数区间内每一个数二进制表示中含有 \(i\)\(1\) 的数的个数

这么分析出来,就可以考虑直接开始记忆化搜索了。

参考代码

50pts 暴力

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e8+5,mod=10000007;
int num[N];
void solve(int x)
{
	int cnt=0;
	int temp=x;
	while(x)
	{
		if(x&1) cnt++;
		x>>=1;
	}
	num[temp]=cnt;
}

signed main()
{
	int n; cin>>n;
	for(int i=1;i<=n;i++)
		solve(i);
	int ans=1;
	for(int i=1;i<=n;i++)
		(ans*=num[i])%=mod;
	cout<<ans<<endl; 
}

100pts正解

#include<iostream>
#include<cstring>
#define int long long
using namespace std;
const int N=50,mod=10000007;
int dp[N][N],num[N];
int dfs(int pos,int sum,bool lim)
{
	if(!pos) return max(sum,(int)1);
	if(!lim && dp[pos][sum]>=0) return dp[pos][sum];
	int top=lim?num[pos]:1;
	int ans=1;
	for(int i=0;i<=top;i++) (ans*=dfs(pos-1,sum+i,lim && i==top))%=mod;
	if(!lim) dp[pos][sum]=ans;
	return ans;
}
int solve(int x)
{
	int idx=0;
	memset(dp,-1,sizeof(dp));
	while(x) 
	{
		num[++idx]=x&1;
		x>>=1;
	}
	return dfs(idx,0,true); 
}
signed main()
{
	int n; cin>>n;
	cout<<solve(n)<<endl;
	return 0;
}

细节实现

强调几个细节。

  • solve()函数中,要记得每次初始化dp[]数组,并且初始为 \(-1\) 这样是因为有的时候,缓存数组中也会存在值为 \(0\) 的特殊情况;
  • 在递归出口的位置,由于是连乘,所以要排除 \(0\) 的影响,这里就是为了避免。

总结归纳

好题。

posted @ 2025-08-01 10:43  枯骨崖烟  阅读(6)  评论(0)    收藏  举报