给定一个二进制表示的数
s
s
s 。
定义函数
f
(
x
)
f(x)
f(x) 为
x
x
x 的二进制位中为
1
1
1 的数量。每次操作可以使得
x
→
f
(
x
)
x\rightarrow f(x)
x→f(x) ,问在最少操作次数下,恰好
k
k
k 次操作后为
1
1
1 的数有多少,答案对
1
0
9
+
7
10^9+7
109+7 取模
这个数 s s s 的上限很大,该怎么考虑呢?
考虑 x x x 为 2 1000 − 1 2^{1000}-1 21000−1 时,其二进制位为 1000 1000 1000 个 1 1 1 , f ( x ) = 1000 f(x)=1000 f(x)=1000
所以只需要暴力计算下 1000 1000 1000 以内的数到 1 1 1 的最小次数即可。
定义 d p [ x ] dp[x] dp[x] 表示 x x x 通过 f f f 函数到达 1 1 1 的最小次数。
状态转移为: d p [ x ] = d p [ f ( x ) ] + 1 dp[x]=dp[f(x)]+1 dp[x]=dp[f(x)]+1
考虑为 d p [ x ] = k − 1 dp[x]=k-1 dp[x]=k−1 的 x x x ,那么二进制位中 1 1 1 的个数为 x x x 的数,到达 1 1 1 的最小次数恰好为 k k k 。
所以我们找到所有 d p [ x ] = k − 1 dp[x]=k-1 dp[x]=k−1 的 x x x ,然后找到小于等于 s s s 的,二进制位中 1 1 1 的数量为 x x x 的所有的数。
这里考虑到数据范围,很容易联想到数位 d p dp dp 。
但是这里由于初始化的问题,容易超时,需要细化数位 d p dp dp 的过程。
令 n n n 为 s s s 的二进制表示的长度,
即如果我们选择 i i i ,
s [ i ] = 1 s[i]=1 s[i]=1
s [ i ] = 0 s[i]=0 s[i]=0 ,则不考虑,因为没有选择, y [ i ] y[i] y[i] 只能为 0 0 0
上述的整体过程,其实就是数位 d p dp dp 的过程。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
#include
using namespace std;
const int MOD = 1e9 + 7;
int qp(int a, int b) {
int res = 1;
while (b > 0) {
if (b & 1) res = 1ll * res * a % MOD;
a = 1ll * a * a % MOD;
b >>= 1;
}
return res;
}
int main()
{
string s;
int k;
cin >> s >> k;
int n = int(s.size());
if (k == 0) {
cout << "1\n";
return 0;
}
if (k == 1) {
cout << n - 1 << "\n";
return 0;
}
vector<int> fac(n + 1), ifac(n + 1);
fac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % MOD;
ifac[n] = qp(fac[n], MOD - 2);
for (int i = n - 1; i >= 0; --i) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % MOD;
auto C = [&](int a, int b) -> int {
if (b < 0 || a < b) return 0;
return int(1ll * fac[a] * ifac[b] % MOD * ifac[a - b] % MOD);
};
int ans = 0;
vector<int> dp(n + 1);
dp[1] = 0;
for (int i = 2; i <= n; ++i) {
int x = i;
int cnt = 0;
while (x > 0) {
cnt += 1;
x -= x & -x;
}
dp[i] = dp[cnt] + 1;
if (dp[i] + 1 == k) {
// 选择 <= s 的,二进制位个数为 i 的数
// 当前第 j 位为 0 ,还剩 n - j - 1 位,要选择这么些个 1
int res = 0;
int one = 0;
for (int j = 0; j < n; ++j) {
if (s[j] == '1') {
// 当前第 j 位有两种选择
// 第 j 位为 0
// 剩余 n - j - 1 位都可以为 1,前面已经占据了 one 个 1,后面再选择 k - one 个即可
res = (res + C(n - j - 1, i - one)) % MOD;
// 第 j 位为 1,如果 j 不是最后一位,即 j + 1 < n ,那么让 one += 1 ,交由后面的位继续考虑
// 如果 j 是最后一位,那么考虑 one + 1 是否等于 k 即可
// 对于后面的位来说 one 增加了
one += 1;
}
if (j + 1 == n && one == i) {
res = (res + 1) % MOD;
}
}
ans = (ans + res) % MOD;
}
}
cout << ans << "\n";
return 0;
}