牛客 19多校7 H 数位dp

给出A, B, C, 在1~A中找一个数x, 1~B中找一个数y, 使得x&y>C或x^y<C, 问这样的二元组(x,y)有多少对.

原来数位dp可以这么骚的...虽然大佬们说这是个裸题...

因为^和&运算都是位运算, 所以把ABC都分为2进制表示.

我们考虑最暴力的做法: 枚举x, 枚举y 判断是不是满足题意. 在这个过程中, 我们发现做了很多无用功, 例如, 如果x和y的高位&答案已经比C的高位要大, x和y的低位不论取什么值不会影响结果, 反之亦然. 所以我们可以从高位向下数位dp, 每个位置保存x和y高位的大小信息. 这个题有趣在于数位dp不仅是关于一个数, 而是关于ABC三个数的. 其实只是在不断的在枚举A里的x和B里的y, C只是用来判断答案. 两个数当然会有两个lim限制, 对于x和y不能取0, 所以要特别记录x和y任意一个不能每一位全为0.

What's New: 我的写法是7维, 2维记录lim, 2维记录高位大小信息, 2维记录全零情况.

实际上, 可以5维, 舍弃记录全零的2维, 这样算出来的是0~A选择x, 0~B选择y的答案, 利用容斥的思想, 把a设为0, 答案减掉, 把a恢复, b设为0, 再算一遍减掉, 再把0 0 的情况加上. 这样, 我们无意中发现, 这样的写法可以解决这样的题:

给出A1, A2, B1, B2, C, 在A1~A2中找一个数x, B1~B2中找一个数y, 使得x&y>C或x^y<C, 问这样的二元组(x,y)有多少对.

通过这个题, 我发现, 数位dp不仅可以解决一个数有限制的问题, 还可以解决两个数有限制的问题.

#include<bits/stdc++.h>
#define enter puts("")
using namespace std;

typedef long long ll;
inline ll read() {
    ll x = 0;
    int f = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') f |= (ch == '-'), ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x = f ? -x : x;
}

inline void write(ll x) {
    if (x == 0) {
        putchar('0');
        return;
    }
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    static char s[23];
    int l = 0;
    while (x != 0)s[l++] = x % 10 + 48, x /= 10;
    while (l)putchar(s[--l]);
}

int _;
ll A, B, C;
int a[40], b[40], c[40];
ll dp[40][2][2][3][3][2][2];
//   pos limA  limB
//   and  a and b compare  c:  <:0  ==:1  >:2
//   xor  a and b compare  c:  <:0  ==:1  >:2
//   0: notZero 1:isAllZero


ll dfs(int pos, int limA, int limB, int andCmp, int xorCmp, int aIsZero, int bIsZero) {
    if (pos <= 0)
        return (andCmp == 2 || xorCmp == 0) && aIsZero == 0 && bIsZero == 0;
    ll &ans = dp[pos][limA][limB][andCmp][xorCmp][aIsZero][bIsZero];
    if (ans != -1)return ans;
    ans = 0;
    int mA = limA ? a[pos] : 1, mB = limB ? b[pos] : 1;
    for (int i = 0; i <= mA; ++i) {
        for (int j = 0; j <= mB; ++j) {
            int nxtAndCmp, nxtXorCmp;
            if (andCmp == 0)nxtAndCmp = 0;
            else if (andCmp == 2)nxtAndCmp = 2;
            else {
                if ((i & j) == c[pos])nxtAndCmp = 1;
                else if ((i & j) < c[pos])nxtAndCmp = 0;
                else nxtAndCmp = 2;
            }
            if (xorCmp == 0)nxtXorCmp = 0;
            else if (xorCmp == 2)nxtXorCmp = 2;
            else {
                if ((i ^ j) == c[pos])nxtXorCmp = 1;
                else if ((i ^ j) < c[pos])nxtXorCmp = 0;
                else nxtXorCmp = 2;
            }
            ans += dfs(pos - 1, limA && i == mA, limB && j == mB,
                       nxtAndCmp, nxtXorCmp, aIsZero && i == 0, bIsZero && j == 0);
        }
    }
    return ans;
}


void init() {
    _ = read();
    while (_--) {
        memset(dp, -1, sizeof(dp));
        A = read(), B = read(), C = read();
        int pos = 0;
        while (pos < 33) {
            a[++pos] = A & 1, b[pos] = B & 1, c[pos] = C & 1;
            A >>= 1, B >>= 1, C >>= 1;
        }
        write(dfs(32, 1, 1, 1, 1, 1, 1));
        enter;
    }

}


int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    init();
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值