Codecraft-18 and Codeforces Round #458 (Div. 1 + Div. 2, combined) C. Travelling Salesman and Specia

题目链接:点击打开链接

题目大意:给出一个二进制数m,这个二进制数中1的个数为n,将m转变为n记做操作一次,知道将m转变为1结束,操作的总次数记为m的步长。

现在给出一个数n,和步长k,求小于等于n的数中有多少个数的步长为k。因为个数过多,需要模109 + 7.

思路:

2的1000次方是一个很大的数,但是只要经过一次操作之后,就会变成一个小于等于1000的数,所以我们可以先预处理出来所有小于等于1000的数需要多少次操作转化为1。

现在的n需要经过x次操作变成1,那么n经过一次操作得到的数就需要经过x-1次操作变成1

根据预处理的结果,我们可以找到哪些数经过x-1次操作变成1

如果找到了数r,那么就需要在小于等于n的范围内,排列r个1

要保证小于等于n的话,可以从最高位开始进行这样的操作,i代表现在的位数,nones表示已经出现了多少个1

当该位为1的时候,我们考虑:

如果将该为的1变为0,则在后面的n.size()-i-1个位置中找j-nones个位置  即C(n.size()-i-1,j-nones)

如果该为1不变,则nones++,继续进行下一位操作

如果该位为0,

则只有直接nones++,继续进行下一位操作

注意点:有三个特例需要注意,如果k==0,则直接输出1

如果k==1,计算的时候不要把1也记为步长为1

因为考虑的时候没有考虑的本身,所以最后要特别检验下自身是否满足

#include <bits/stdc++.h>
using namespace std;


#define MOD 1000000007

int dp[1004];
long long ncr[1004][1004];

int ones(int n)
{
    int cnt = 0;
    while(n)
    {
        if(n%2 == 1)
        {
          cnt++;
        }
        n /= 2;
    }
    return cnt;
}

void calcncr()//预处理组合数
{
    for(int i = 0; i <= 1000; i++)
    {
        ncr[i][0] = 1;
    }
    for(int i = 1; i <= 1000; i++)
    {
        for(int j = 1; j <= 1000; j++)
        {
          ncr[i][j] = (ncr[i-1][j-1] + ncr[i-1][j])%MOD;
        }
    }
}

int main()
{
    string n;
    int k;
    calcncr();

    dp[1] = 0;
    for(int i = 2; i <= 1000; i++)//预处理小于等于1000的数到1的步数
    {
        dp[i] = dp[ones(i)] + 1;
    }

    cin >> n >> k;

    if(k == 0)
    {
        cout << "1\n";
        return 0;
    }

    long long nones = 0, ans = 0;
    for(int i = 0; i < n.size(); i++)
    {
        if(n[i] == '0')
        {
            continue;
        }
        for(int j = max(nones, 1LL); j < 1000; j++)
        {
          if(dp[j] == k-1)
          {
            ans = (ans + ncr[n.size()-i-1][j-nones])%MOD;
            if(i == 0 && k == 1)//这里主要去掉1本身这种特殊的情况 1到1的步数是0不是1
            {
              ans = (ans+MOD-1)%MOD;
            }
          }
        }
        nones++;
    }

    int cnt = 0;
    for(int i = 0; i < n.size(); i++)
    {
        if(n[i] == '1')
        {
          cnt++;
        }
    }
    if(dp[cnt] == k-1)//加上自己本身
    {
        ans = (ans + 1)%MOD;
    }
    cout << ans << endl;

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值