题面:
给定一个 N N N 和 K K K,求 N N N 以内的数中多少个数的二进制有 K K K 个 1 1 1。
Solution:
数位 DP 水题。因为 1 ≤ N ≤ 1 0 18 1\leq N\leq10^{18} 1≤N≤1018,暴力枚举一定会炸,所以考虑用数位 DP。我们先把 N N N 转换为二进制,存放在数组 a a a 中,再暴搜每一种可能,如果没有超出原来给定的范围,就再搜下一个点。最后,可以加上记忆化优化。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200;
ll n,k,a[N],f[N][2][N];
ll dfs(ll w,ll zt,ll sum)
//w表示当前剩余位数,zt表示当前状态。如果是0表示前面几位并没有满,1则表示前面几位已经满了。
//sum表示到当前位置1的个数
{
ll i,ans=0;//ans表示从当前位置的所有可能性总数
if(w==0) return (sum==k);//如果每一位都做过,就返回
if(f[w][zt][sum]!=-1) return f[w][zt][sum];//如果做过,就返回答案
for(i=0;i<2;++i)//每个点有0,1两种可能
{
if(zt&&i>a[w]) break;//如果超出原来的范围就停止
ans+=dfs(w-1,zt&&i==a[w],sum+i);//dfs(前一个位置,状态,如果是1则让1的个数增加1)
}
return f[w][zt][sum]=ans;//记忆化
}
ll digit(ll x)
{
ll cnt=0;//保存转换为二进制的位数
memset(f,-1,sizeof(f));//记忆化,清空f数组
while(x)//转换二进制
{
a[++cnt]=x%2;
x/=2;
}
return dfs(cnt,1,0);
}
signed main()
{
cin>>n>>k;
cout<<digit(n);//输出N以内有多少个数二进制中有K个1
}
提示:不开 long long 见祖宗!