题目描述
对于一个正整数x,我们定义一次操作是将其变为它二进制下“1”的个数,比如我们知道1310?=11012 ,而1101有三个"1",所以对13进行一次操作就会将其变为3。显而易见的是,对于一个正整数,我们在进行若干次操作后,一定会将其变为1。
给定n和k,其中n是在二进制下被给出,求出所有不大于n且将其变为1的最小操作次数为k的数的个数对10^9+7取模的结果。
1<=n<21000, 0<=k<=10000
题解
因为n在二进制下长度最大为1000,所以最多有1000个1,所以转化一次最多是1000。
我们可以处理出当前数为x时还需要几步到1,cnt[i]=cnt[sum[i]]+1,sum[i]为i在二进制下有多少个1,sum[i]=sum[i^lowbit(i)]+1,i^lowbit(i)就是i在二进制下去掉最右边的1,这个数一定先处理出来了,所以就可以递推,x在1000以内。
然后就可以进行数位DP,f[s][num][lim]表示当前处理到第i位,1的个数为num
那么最后cnt[num]+1==k就是一种,num就是枚举出的数转换一次得到的。
然后就WA了....
考虑k=0时,会输出0,但是容易发现1就是一个解,这是因为cnt是操作了一次之后,而1不需要操作,所以特判k=0即可。
k=1时,答案会多2,这是因为0和1的关系,所以把cnt[0]设成负数,然后特判k=1时答案-1即可。
#include<bits/stdc++.h> using namespace std; const int mod=1000000007; const int maxn=1005; char s[maxn]; int k,len,num[maxn]; int sum[maxn],cnt[maxn]; int f[maxn][maxn][2]; void init(){ sum[0]=0; cnt[1]=0; cnt[0]=-2; for(int i=1;i<=1000;i++) sum[i]=sum[i^(i&-i)]+1; for(int i=2;i<=1000;i++) cnt[i]=cnt[sum[i]]+1; } int dfs(int s,int c,bool lim){ if(!s) return cnt[c]+1==k; if(f[s][c][lim]!=-1) return f[s][c][lim]; int mx= lim ? num[s] : 1; int ret=0; for(int i=0;i<=mx;i++) ret=(ret+dfs(s-1,c+(i==1),lim&&i==mx))%mod; return f[s][c][lim]=ret; } int cx(){ memset(f,-1,sizeof(f)); return dfs(len,0,true); } int main(){ init(); scanf("%s%d",s,&k); if(!k){printf("1");return 0;} len=strlen(s); for(int i=1;i<=len;i++) num[i]=s[len-i]-‘0‘; int ans=cx(); if(k==1) ans--; printf("%d",ans); }
网上都是什么组合数,明明就有数位DP的标签(掩饰自己不会组合数)
原文地址:https://www.cnblogs.com/sto324/p/11401958.html
时间: 2024-11-08 23:59:46