题目大意
Bob 想了一个 1 到 n 之间的数字(n = 2^d)。Alice 初始知道这个数字是奇数还是偶数。
Alice 每回合可以选择:
将当前数字除以 2(仅当数字为偶数时)
将当前数字减 1
每次操作后,Bob 会告诉 Alice 一个非负整数 x,表示当前数字 a 能被 2^x 整除,但不能被 2^(x+1) 整除。如果数字变为 0,Alice 获胜。
游戏最多进行 k 回合。求有多少个初始数字(1 到 n)使得 Alice 在最优策略下无法在 k 回合内获胜。
ABC-387-C - Snake Numbers
HDU - 2089- 不要62
思路讲解
在题目要求下,一个数字要删多少次有规律吗?其实就是 popcount ( x ) + ⌊ log 2 ( x ) ⌋ \text{popcount}(x)+\lfloor\log_{2}(x)\rfloor popcount ( x ) + ⌊ log 2 ( x ) ⌋
然后问题就来到了这个怎么样快速统计了,这个是这道题目的难点。当然,显然我们应该使用这个数位 dp。
那么我们一般数位 dp 采用的是这个记忆化搜索,我们定义搜索递归函数为 d f s ( l e n , l i m i t ) dfs(len,limit) d f s ( l e n , l i m i t ) ,d p l e n , l i m i t , v a l dp_{len,limit,val} d p l e n , l i m i t , v a l 表示在 $len $ 长度下,l i m i t limit l i m i t 受或不受 n n n 高位限制的情况下,数字所需消耗次数为 v a l val v a l 的数有几个。
然后我们来想一想,就是这个普通搜索,我们要知道 d f s ( l e n , l i m i t ) dfs(len,limit) d f s ( l e n , l i m i t ) 的结果,进入到 d f s ( l e n , l i m i t ) dfs(len,limit) d f s ( l e n , l i m i t ) 之后,我们需要搜什么?显然,我们需要搜索 d f s ( l e n − 1 , ? ) ⋯ d f s ( l , ? ) ⋯ d f s ( 0 , ? ) dfs(len-1,?)\cdots dfs(l,?)\cdots dfs(0,?) d f s ( l e n − 1 , ? ) ⋯ d f s ( l , ? ) ⋯ d f s ( 0 , ? ) ,因为我们要搜索所有可能会转移到这个状态的状态 。
从状态 l → l e n l\to len l → l e n ,那么会增加多少消耗次数呢?Δ ← l e n − l + 1 \Delta \gets len-l+1 Δ ← l e n − l + 1 (长度增加 l e n − l len-l l e n − l ,还增加了一个 1)。
⚠️ 注意:从 0 转移过来的特殊情况 ,Δ ← l e n − l \Delta \gets len-l Δ ← l e n − l ,如果你像我一样认为 0 的二进制长度为 1 的话(这个是因为,我们虽然认为 0 的二进制长度为 0,但是实际上我们的 l e n len l e n 并没有增加 l e n len l e n 的长度,只增加了 l e n − 1 len-1 l e n − 1 的长度)。
那么前面讲的有点抽象,举个例子就是 ,( 1011 ) 2 (1011)_{2} ( 1 0 1 1 ) 2 就是从 ( 11 ) 2 (11)_{2} ( 1 1 ) 2 转移过来的,因为我们的状态设计是不包含前导 0 的。当然,如果 ( 1011 ) 2 (1011)_{2} ( 1 0 1 1 ) 2 就是题目中的 n 的话(即表示二进制长度为 4 的上限限制),除了 ( 11 ) 2 (11)_{2} ( 1 1 ) 2 ,还可以从 ( 1 ) 2 (1)_2 ( 1 ) 2 ,( 0 ) 2 (0)_2 ( 0 ) 2 转移过来,但是显然不能从 ( 100 ) 2 (100)_2 ( 1 0 0 ) 2 等二进制长度为 3 的状态转移过来。
最后统计答案的时候,需要将这个 d f s ( l e n , 1 ) , d f s ( l e n − 1 , 0 ) , ⋯ , d f s ( 0 , 0 ) dfs(len,1),dfs(len-1,0),\cdots,dfs(0,0) d f s ( l e n , 1 ) , d f s ( l e n − 1 , 0 ) , ⋯ , d f s ( 0 , 0 ) 的状态结果累和得到答案。
AC代码
AC
https://codeforces.com/contest/2184/submission/357806886
源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 #include <bits/stdc++.h> #include <ranges> #define all(vec) vec.begin(),vec.end() #define lson(o) (o<<1) #define rson(o) (o<<1|1) #define SZ(a) ((long long) a.size()) #define debug(var) cerr << #var <<":" <<var<<"\n" ; #define cend cerr<<"\n-----------\n" #define fsp(x) fixed<<setprecision(x) using namespace std;using ll = long long ;using ull = unsigned long long ;using DB = double ;using LD = long double ;using CD = complex<double >;static constexpr ll MAXN = (ll)1e6 +10 , INF = (1ll <<61 )-1 ;static constexpr ll mod = 998244353 ; static constexpr double eps = 1e-8 ;const double pi = acos (-1.0 );ll lT; template <class T >constexpr int lg_bw (T x) {#ifdef LOCAL static_assert (std::is_integral_v<T>, "__lg_bw requires integral type" ); #endif using U = std::make_unsigned_t <T>; U u = static_cast <U>(x); #ifdef LOCAL assert (u != 0 ); #endif return static_cast <int >(std::bit_width (u)) - 1 ; } void Solve () { ll N,K; cin >> N >> K; vector dp (35 ,vector(2 ,vector<ll>(75 ))) ; vector vis (35 ,vector<char >(2 )) ; bitset<35> bin (N) ; auto dfs=[&](auto && self,ll len,bool limit) { if (len==0 ) { dp[len][limit][0 ]=1 ; return ; } if (vis[len][limit]) { return ; } vis[len][limit]=true ; if (limit) { int pos_encounter_1=-1 ; for (int l=len-1 ;l>=0 ;--l) { if (l==0 || bin[l-1 ]) { pos_encounter_1=max (pos_encounter_1,l); } if (pos_encounter_1==-1 ) { continue ; } ll diff=len+1 -l; if (l==0 ) { diff=len-l; } if (pos_encounter_1==l) { self (self,l,limit); for (int val=diff;val<=70 ;++val) { dp[len][limit][val]+=dp[l][limit][val-diff]; } }else { self (self,l,false ); for (int val=diff;val<=70 ;++val) { dp[len][limit][val]+=dp[l][false ][val-diff]; } } } }else { for (int l=len-1 ;l>=0 ;--l) { ll diff=len+1 -l; if (l==0 ) { diff=len-l; } self (self,l,false ); for (int val=diff;val<=70 ;++val) { dp[len][limit][val]+=dp[l][false ][val-diff]; } } } }; dfs (dfs,lg_bw (N)+1 ,true ); dfs (dfs,lg_bw (N),false ); vector<ll> acc (75 ) ; for (int val=0 ;val<=70 ;++val) { for (int len=lg_bw (N);len>=0 ;--len) { acc[val]+=dp[len][false ][val]; } acc[val]+=dp[lg_bw (N)+1 ][true ][val]; } ll ans=0 ; for (int i=K+1 ;i<=70 ;++i) { ans+=acc[i]; } cout<<ans<<"\n" ; } signed main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); cout.tie (nullptr ); cin >> lT; while (lT--) Solve (); return 0 ; }
心路历程(WA,TLE,MLE……)
向左短路 RE 了。
首先,不难发现,在题目要求下,我们只能够在二进制第一个 1 出现位置进行操作,把这个 1 后面出现的所有 0 全部消除。所以说,一个数字要删多少次有规律吗?
首先,不妨定义状态,d p i dp_i d p i 指的是
当然,虽然知道了要使用数位 dp,如何实现也至少是需要想一想的。虽然说数位 dp 一般采用这个自顶向下的记忆化搜索设计方法,但是这道题目没什么限制条件,以计数为主,直接采用这个递推法即可。
d p l e n , i dp_{len,i} d p l e n , i 表示在该(二进制)长度下,v a l = i val=i v a l = i 的这个数字的数量。
d p l e n , i ← ∑ l = 0 l e n − 1 d p l , i + 1 + l e n − l dp_{len,i}\gets \sum_{l=0}^{len-1}dp_{l,i+1+len-l}
d p l e n , i ← l = 0 ∑ l e n − 1 d p l , i + 1 + l e n − l
那么我们一般数位 dp 采用的是这个记忆化搜索,我们就先想搜索吧,搜索 [ 0 , n ] [0,n] [ 0 , n ] 有几个数字的 v a l > k val>k v a l > k 。当然这个有点奇怪,我们不妨假设我们是求的这个 [ 0 , n ] [0,n] [ 0 , n ] 下,v a l = i val=i v a l = i 的这个数字的数量,即 d p i dp_i d p i 。
那我们要搜索这个东西,需要什么呢?d f s ( l e n , i s m a x ) dfs(len,ismax) d f s ( l e n , i s m a x ) 可以吗?我们先试试,我们首先发现,只有 d p i dp_{i} d p i 无法满足这个我们的要求,设置为 d p l e n , i dp_{len,i} d p l e n , i 更为合适。