CF914C - Travelling Salesman and Special Numbers (数位dp)

题目定义了一种操作:将一个数变为这个数二进制下的1的个数。询问有多少个不大于n的数,进行了恰好k次操作后等于1。

数据范围(1<n<21000),观察可知,任意一个大于1000的数进行一次操作后都会变成小于等于1000的数,因此可以枚举1000以内的数变为1的操作个数,当某个数x的操作数为m-1时,统计长度小于等于n的位数下所有满足1的个数为x的数的数量,易知1的位置摆放为组合,所以需要预处理组合数

 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;

const int mod=1000000007;
char s[1314];
ll C[1010][1010];
ll l;
int a[1314];
int num[1314];
ll total=0;

int wei(ll x)
{
    int cnt=0;
    while(x)
    {
        cnt+=(x&1);
        x>>=1;
    }
    return cnt;
}
void dfs(int dep,int now,int flag)//d几位now几个1 
{
    if(now>l)return;
    if(now==0){
        total=(total+1)%mod; 
        return;
    }
    if(dep==0)return;
    if(flag==0)//放0则后面若干个1排列组合 
    {
        total=(total+C[dep][now])%mod;
    }
    else {
        if(a[dep]==0)
        {
            dfs(dep-1,now,flag);
        }
        if(a[dep]==1)
        {
            dfs(dep-1,now-1,flag);
            dfs(dep-1,now,0);
        }
    }
}

int main(){
    ll m;
    cin>>s+1;
    l=strlen(s+1);
    cin>>m;
    if(m==0)
    {
        cout<<1;
        return 0;
    }
    if(m==1)
    {
        cout<<l-1;
        return 0;
    }
    for(int i=0;i<=l;i++)
    {
        C[i][0]=1;
    }
    for(int i=1;i<=l;i++)
    {
        for(int j=1;j<=i;j++)
        {
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
        //    cout<<C[i][j]<<" ";
        }
        //cout<<endl;
    }
    for(int i=1;i<=l;i++)
    {
        a[l-i+1]=s[i]-0;
    }
    /*for(int i=l;i>=1;i--)
    {
        cout<<a[i];
    }
    cout<<endl;*/
    num[1]=0;
    for(int i=2;i<=1000;i++)
    {
        int w=wei(i);
        //cout<<w<<endl;
        num[i]=num[w]+1;
        if(num[i]==m-1)
        {
            dfs(l,i,1);
        }
    }
    cout<<total;
    return 0;
}

 

CF914C - Travelling Salesman and Special Numbers (数位dp)

上一篇:二分查找的原理及其应用


下一篇:NoSql数据库初探-mongoDB读操作