本文主要是介绍【每日一题】AcWing 5271. 易变数 | 思维 | 中等,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
题目内容
原题链接
给定一个二进制表示的数 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 取模
数据范围
- 1 ≤ s < 2 1000 1\leq s<2^{1000} 1≤s<21000
- 0 ≤ k ≤ 1000 0\leq k\leq 1000 0≤k≤1000
题解
这个数 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
- 考虑这里选择的数 y y y 的 y [ i ] = 0 y[i]=0 y[i]=0 ,则 y [ i + 1 , ⋯ s − 1 ] y[i+1,\cdots s-1] y[i+1,⋯s−1] 都可以为 1 1 1 ,即有 s − i − 1 s-i-1 s−i−1 个位置中,选择若干个位置为 1 1 1 ,这个若干是由剩余可选位置来决定的。
我们需要有 x x x 位 1 1 1 ,而 s [ 0 , i ] s[0,i] s[0,i] 中有 m m m 个为 1 1 1 ,那么我们可以选择的就是 m − x m-x m−x 位。即 C ( s − i − 1 , m − x ) C(s-i-1,m-x) C(s−i−1,m−x) - 考虑这里选择的数 y y y 的 y [ i ] = 1 y[i]=1 y[i]=1 ,则后面只能选择 m − x − 1 m-x-1 m−x−1 个 1 1 1 了。但是这样直接考虑会超 s s s 的上限,所以将这个问题继续往后抛。直到最后如果 s s s 的二进制位为 1 1 1 的数量恰好为 x x x ,则答案 + 1 。
- 考虑这里选择的数 y y y 的 y [ i ] = 0 y[i]=0 y[i]=0 ,则 y [ i + 1 , ⋯ s − 1 ] y[i+1,\cdots s-1] y[i+1,⋯s−1] 都可以为 1 1 1 ,即有 s − i − 1 s-i-1 s−i−1 个位置中,选择若干个位置为 1 1 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 <bits/stdc++.h>
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 位,要选择这么些个 1int 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;
}
这篇关于【每日一题】AcWing 5271. 易变数 | 思维 | 中等的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!