题解 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\) 的影响,这里就是为了避免。
总结归纳
好题。

浙公网安备 33010602011771号