codeforces914C Travelling Salesman and Special Numbers 二进制特性+组合数学+递推

本文介绍了一种使用数位动态规划(数位DP)解决特定类型数学问题的方法。问题要求计算在限定次数内通过特定操作使数值变为1的所有可能数值的数量。文章详细解释了如何预处理数据、应用组合数原理并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

比较骚的一道题。。
题意大概是,我们定义一次操作αα:将一个数ii二进制是1的位数取出来。
比如说,1010=10102,所以α(10)=2α(10)=2
显然,任何数数在经过多次αα操作之后,一定能够成为1。现在有一个数i(i21000)i(i≤21000),求一共有多少数mmk次变化之后可以变成1,并且mim≤i
答案对109+7109+7取模。


首先,我们发现一个性质:由于这个数是小于2100021000的,所以在第一次的变化之后结果一定是小于1000的。
因此,我们可以想到预处理出来1000以内的数需要多少次变化,而这一过程可以递推求解:f[i]=f[getbit(i)]+1f[i]=f[getbit(i)]+1,其中getbit()是用来求出一个数二进制下有多少位是1的:

inline int getbit(int x) {
    int ans = 0;
    while(x) {
        if(x & 1) ++ans;
        x >>= 1;
    }
    return ans;
}

然后呢?现在的问题集中在,如果给定ii,如何求出比i小并且位数的dp值是k1k−1的,也就是变化之后只需要k1k−1次。
不妨讨论一下如果某个位是0,那么我们就可以忽略它,而如果某个位是1,那么它之后的每个值全部可以变,前提是只需要它变成0。
所以,我们发现,对于第pos位来说,假如前面有mm个位是1:

ans+=ni=mCnposim[dp[i]==k1]

这是因为,对于某个确定的位置,如果前面的部分已经全部确定,那么后面的部分就可以自由选择了。所以,后面的就可以用组合数去求!
还有一个问题,就是为什么我们只考虑某一位变成0后面的随意改变的情况。这是因为,这一位是1的情况我们在对这位之前的东西进行求解的时候已经处理过了。
而组合数怎么求?用递推法预处理:Cnm=Cn1m1+Cn1mCmn=Cm−1n−1+Cmn−1
还有三个特例需要注意:
如果1的位数的dp值恰是k1k−1,那么结果还要加1。这是因为我们在枚举的时候已经忽略的第一位的情况,所以现在要再考虑进来。
如果k=0k=0,那么结果是0,因为1不在答案里。
如果k=1k=1,那么结果要-1,也是因为1不在答案里。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MOD = (LL)1e9+7;
LL k, C[1003][1003], dp[1003], cnt, num;
string str;
inline int getbit(int x) {
    int ans = 0;
    while(x) {
        if(x & 1) ++ans;
        x >>= 1;
    }
    return ans;
}
int main() {
    cin>>str>>k;
    if(!k) return puts("1"), 0;
    for(int i = 0; i <= 1000; ++i)
        C[i][0] = 1;
    for(int i = 1; i <= 1000; ++i) {
        for(int j = 1; j <= 1000; ++j)
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
    }
    dp[1] = 0;
    for(int i = 2; i <= 1000; ++i)
        dp[i] = dp[getbit(i)] + 1;
    int N = str.size();
    for(int i = 0; i < N; ++i) {
        if(str[i] == '0') continue;
        for(int j = max(1LL, cnt); j < N; ++j) {
            if(dp[j] == k - 1) {
                num = (num + C[N - 1 - i][j - cnt]) % MOD;
            }
        }
        ++cnt;
    }
    if(k == 1) --num;
    if(dp[cnt] == k - 1) num = (num + 1) % MOD;
    cout<<num<<endl;
    return 0;
}

感觉是很精妙的思想…原来这个就是数位DP吗(x

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值