CF914C Travelling Salesman and Special Numbers

题目描述

对于一个正整数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);
}
View Code

 

网上都是什么组合数,明明就有数位DP的标签(掩饰自己不会组合数)

 

posted @ 2019-08-23 18:37  _JSQ  阅读(197)  评论(0编辑  收藏  举报