比较骚的一道题。。
题意大概是,我们定义一次操作αα:将一个数ii二进制是1的位数取出来。
比如说,,所以α(10)=2α(10)=2。
显然,任何数数在经过多次αα操作之后,一定能够成为1。现在有一个数i(i≤21000)i(i≤21000),求一共有多少数mm在次变化之后可以变成1,并且m≤im≤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,如何求出比小并且位数的dp值是k−1k−1的,也就是变化之后只需要k−1k−1次。
不妨讨论一下如果某个位是0,那么我们就可以忽略它,而如果某个位是1,那么它之后的每个值全部可以变,前提是只需要它变成0。
所以,我们发现,对于第pos位来说,假如前面有mm个位是1:
这是因为,对于某个确定的位置,如果前面的部分已经全部确定,那么后面的部分就可以自由选择了。所以,后面的就可以用组合数去求!
还有一个问题,就是为什么我们只考虑某一位变成0后面的随意改变的情况。这是因为,这一位是1的情况我们在对这位之前的东西进行求解的时候已经处理过了。
而组合数怎么求?用递推法预处理:Cnm=Cn−1m−1+Cn−1mCmn=Cm−1n−1+Cmn−1。
还有三个特例需要注意:
如果1的位数的dp值恰是k−1k−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