题解:CF914C Travelling Salesman and Special Numbers

题意

定义 popcount(x)x 二进制下 1 的个数。

定义对 x 的一次操作:xpopcount(x),显然任意正整数经过若干次操作后会变为 1

给定 nk,其中 n 是在二进制下被给出,求出所有不大于 n 且将其变为 1 的最小操作次数为 k 的数的个数对 109+7 取模的结果。

分析

因为 n<21000,所以数 x(x<n) 经过 1 次操作后得到的 x1000

所以我们可以预处理 [2,1000] 中的数变成 1 的最少操作次数。

考虑在二进制下使用数位 dp 解决。

定义 dpn,i,v 为目前搜索至第 n 位,该位之前有 i1,上一位填的是 v 的满足题意的数的个数。

记忆化搜索即可,时间复杂度 O(log2n)

AC 记录

Code

#include<bits/stdc++.h>
using namespace std;
#define maxn 1003
#define popc __builtin_popcount
#define mod 1000000007
int ks[maxn];
void init()
{
for(int i=2;i<=1000;i++)
ks[i]=ks[popc(i)]+1;
}
string s;
bitset<maxn> bs;
int dp[maxn][maxn][2], k;
int dfs(int n, int cnt=0, bool la=0, bool lim=1)
{
if(!lim&&~dp[n][cnt][la]) return dp[n][cnt][la];
if(!n) return cnt==1&&la?0:cnt&&ks[cnt]==k-1;
int ret=0, up=lim?bs[n]:1;
for(int i=0;i<=up;i++)
ret=(ret+dfs(n-1, cnt+i, i, lim&&i==up))%mod;
if(!lim) dp[n][cnt][la]=ret;
return ret;
}
int main()
{
init();
memset(dp, -1, sizeof dp);
cin>>s>>k;
if(k==0) return cout<<1, 0;
for(int i=0;i<s.size();i++)
bs[s.size()-i]=s[i]^48;
cout<<dfs(s.size());
}

本文作者:redacted-area

本文链接:https://www.cnblogs.com/redacted-area/p/18389465

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

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