Codeforces 1330D - Dreamoon Likes Sequences (位运算-异或,dp)
思路
异或又叫做半加,它与和加法的区别就是不会进位。可知
\(a+b=(a⊕b)+2(a\&b)\)
\(2(a\&b)\)就是进位的部分的值。
对于这一题,\(b_i=b_{i-1}⊕a_i\)可以写成\(b_i=b_{i-1}+a_i-2(a_i\&b_{i-1})\)。
因为\(b_i\)是单调递增的,所以有\(b_i-b_{i-1}=a_i-2(a_i\&b_{i-1})>0\),即需要满足\(a_i>2(a_i\&b_{i-1})\)。
如果\(a_i\&b_{i-1}\)这一项最高位的1和\(a_i\)的相等,那么\(2(a_i\&b_{i-1})\)必定大于\(a_i\)。
所以\(a_i\&b_{i-1}\)最高位的1低于\(a_i\)的,即\(b_{i-1}\)的最高位低于\(a_i\)的。
因为\(b_{i-1}=a_{1}⊕...⊕a_{i-1}\),由归纳法可知道\(a_i\)的最高位的1所在的位置是单调递增的。
而且只要保证\(a_i\)位数严格递增,就必定满足\(a_i>2(a_i\&b_{i-1})\)。当\(a_i\)位数为n时,它有\(2^{n-1}\)种情况。
所以按照位数递增来dp就好了。复杂度是\(O((log_2n)^3)\)。未满最高位的部分单独处理。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 50;
ll dp[N][N];
void work(int n, ll m) {
for(int i = 1; i <= n; i++)
dp[i][i] = 1;
for(int b = 1; b <= n; b++) { //终点的位数
for(int a = b - 1; a >= 1; a--) { //起点的位数
dp[a][b] = 0;
for(int i = a + 1; i <= b; i++) {
dp[a][b] += (1ll << (i - 1)) * dp[i][b] % m;
dp[a][b] %= m;
}
}
}
}
int main() {
ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--) {
ll d, m;
cin >> d >> m;
int cnt = 0;
ll td = d;
while(td) {
cnt++;
td >>= 1;
}
work(cnt - 1, m);
ll ans = 0;
ll k = d - (1 << (cnt - 1)) + 1; //未满的部分单独处理
for(int i = 1; i <= cnt - 1; i++) {
for(int j = i; j <= cnt - 1; j++) {
ans += (((1ll << (i - 1)) * dp[i][j] % m) * k) % m;
ans %= m;
ans += ((1ll << (i - 1)) * dp[i][j] % m) % m;
ans %= m;
}
}
cout << (ans + k) % m << endl;
}
}
Update
看了官方题解,才发现dp麻烦了。对于每个位数v,有区间\([2v,min(2(v+1)−1,d)]\)那么多种数可以选择,即\((min(2(v+1)−1,d)−2v+1)+1\)种选择(不选这个区间的数也是一种选择,故加1)。全部乘起来再减1(减去全部不选的方案)就是答案。这样处理好简单。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 50;
int main() {
ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--) {
int n, m;
cin >> n >> m;
ll ans=1;
for(int i = 0; i < N; i++) {
if(n < (1 << i)) break;
ans = ans * (min((1 << (i+1)) - 1, n) - (1 << i) + 2) % m;
}
ans--;
if(ans < 0) ans += m;
cout << ans << endl;
}
}