Codeforces 1330D - Dreamoon Likes Sequences (位运算-异或,dp)

Description

思路

异或又叫做半加,它与和加法的区别就是不会进位。可知
\(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;
    }
}
posted @ 2020-04-04 21:35  limil  阅读(188)  评论(0编辑  收藏  举报