既然选择了远方,便只顾风雨兼行|

H_W_Y

园龄:1年10个月粉丝:28关注:15

2023-04-15 13:23阅读: 23评论: 0推荐: 0

[P4317 花神的数论题]题解

P4317 花神的数论题【数位DP】

题目描述

最开始其实没有什么想法
第一次遇见数位dp求相乘的题
想就按照常规做法来做,但不知道如何去处理*
于是写了一个错误的代码

//当前枚举到第id位,前面的1的个数为sum,是否达到上限limit
ll dfs(int id,int sum,int limit){
  //1.出口
  if(id==0) return 1ll*sum;
  if(dp[id][sum][limit]!=-1) return dp[id][sum][limit];
  //2.能做的事情 
  int bound=limit?a[id]:1;
  ll res=1;
  for(int i=(id==1?1:0);i<=bound;i++)
    res=1ll*res*dfs(id-1,sum+(i==1),limit&&i==bound)%mod;
  return dp[id][sum][limit]=res%mod;
} 

ll solve(ll x){
  len=0;
  memset(dp,-1,sizeof(dp));
  while(x){
  	a[++len]=x%2;
  	x/=2;
  }
  return dfs(len,0,1);
}

很明显我知道是错的
但又不太清楚是哪里错了
应该是在乘法的记忆化上面出了一些问题
提交上去也只有10分
……
[看了看题解]
……
我们不妨换一个角度来思考
它要求每一个数的sum乘积
由于n1015
所以其实sum是有限的
也就是050中的数
所以我们可以把每一个sum的个数都求出来再用快速幂乘起来
这样就是一个很典型的数位dp了
但对于每一个不同的sum
我们都要用一次dfs去求,不然是无法用上记忆化
会导致TLE

//当前枚举到第id位,前面1的数量为sum,正在统计num的个数,是否达到上限limit
//统计cnt[sum],sum出现的次数 
ll dfs(int id,int sum,int num,int limit){
  //1.出口
  if(id==0) return sum==num;
  if(dp[id][sum][num][limit]!=-1) return dp[id][sum][num][limit];
  //2.能做的事情 
  int bound=limit?a[id]:1;
  ll res=0;
  for(int i=0;i<=bound;i++)
    res+=dfs(id-1,sum+(i==1),num,limit&&i==bound); 
  return dp[id][sum][num][limit]=res;
}

ll quickpow(ll a,ll b){
  ll res=1;
  while(b){
    if(b&1) res=res*a%mod;
    a=a*a%mod;
    b/=2;
  }
  return res%mod;
}

int main(){
  /*2023.4.15 hewanying P4317 花神的数论题 数位DP*/ 
  scanf("%lld",&n);
  memset(dp,-1,sizeof(dp));
  len=0;
  while(n){
  	a[++len]=n%2;
  	n/=2;
  }
  ans=1ll;
  for(int i=1;i<=len;i++) 
    ans=ans*quickpow(1ll*i,dfs(len,0,i,1))%mod;
  printf("%lld\n",ans);
  return 0;
}

总结

对于这种要转个弯的数位dp题,我们的目的还是要把乘积转化成加法
考虑答案的构成,把相同的sum放在一块
这样就可以把题目中的Π转化成Σ
便可以用数位dp解决了

本文作者:H_W_Y

本文链接:https://www.cnblogs.com/H-W-Y/p/17320946.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   H_W_Y  阅读(23)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起