2019 牛客 多校7 H Pair(数位dp)

本文介绍了一种使用数位动态规划(DP)的方法来解决涉及两个数的特定运算问题,即求解在给定范围内(x, y)对数,满足x*y>C或x^y<C的条件。通过定义状态转移方程,文章详细解释了如何逆向思考问题,以减少不必要的计算,并提供了一个高效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接

题意:

给出A、B、C,求 x &amp; y &gt; C x \&amp; y &gt; C x&y>C x y &lt; C x ^ y &lt; C xy<C的(x,y)对数( 1 &lt; = x &lt; = A , 1 &lt; = y &lt; = B 1&lt;=x&lt;=A,1&lt;=y&lt;=B 1<=x<=A,1<=y<=B)


思路:

一开始不知道怎么做,以前只做过一个数的数位dp,没想到两个数也可以做。

正着想的话就会有三种情况,倒着想的话只要总的减去x&y<C&&x^y>C就好了。

定义 d p [ p o s ] [ l i m a ] [ l i m b ] [ a n d ] [ x o r ] [ z a ] [ z b ] dp[pos][lima][limb][and][xor][za][zb] dp[pos][lima][limb][and][xor][za][zb]
pos:当前到第几位
lima:数字x是否达到上限
limb:数字y是否达到上限
and:x和y与运算是否小于C
xor:x和y亦或运算是否大于C
za:x是否出现过非0的数字
zb:y是否出现过非0的数字

从高位枚举,如果之前x和y相与已经小于C了,那么后面都会小于C。同样如果之前x和y亦或已经大于C了,那么之后都会一直大于C。注意x和y中有一个是全0都是不合法的情况。

最后A*B减去数位dp求出来的值就是最后的答案了。


参考代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn =50;
bool a[maxn];
bool b[maxn];
bool c[maxn];
ll dp[50][2][2][2][2][2][2];

ll dfs(int pos, bool lima,bool limb,bool ad,bool xr,bool za,bool zb){
    if(pos<0){
        return za&&zb;
    }
    if(dp[pos][lima][limb][ad][xr][za][zb]!=-1)return dp[pos][lima][limb][ad][xr][za][zb];
    int topa=lima?a[pos]:1;
    int topb=limb?b[pos]:1;
    ll tmp=0;
    for(int i=0;i<=topa;i++){
        for(int j=0;j<=topb;j++){
            if(!ad&&((i&j)>c[pos]))continue;
            if(!xr&&((i^j)<c[pos]))continue;
            tmp+=dfs(pos-1,lima&&(i==topa),limb&&(j==topb),ad||((i&j)<c[pos]),xr||((i^j)>c[pos]),za||i,zb||j);
        }
    }
    return dp[pos][lima][limb][ad][xr][za][zb]=tmp;
}


int main(){
    int t;
    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++){
        memset(dp,-1, sizeof(dp));
        int aa,bb,cc;
        scanf("%d%d%d",&aa,&bb,&cc);
        ll ans=1ll*aa*bb;
        //printf("%lld\n",ans);
        for(int i=0;i<=30;i++){
            a[i]=aa&1;
            aa>>=1;
        }
        for(int i=0;i<=30;i++){
            b[i]=bb&1;
            bb>>=1;
        }
        for(int i=0;i<=30;i++){
            c[i]=cc&1;
            cc>>=1;
        }
        printf("%lld\n",ans-dfs(30, true,true, false,false,false,false));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值