2019牛客暑期多校训练营(第七场)H Pair(数位dp)

题意:给你三个数,A,B,C,计算 有多少对数,满足  x & y > c || x ^ y < c ,其中(1 <= x <= A ,1 <= y <=  B)。

思路:我们可以首先求出他的反面,即有多少对数满足 【 x & y <= c   &&   x ^ y >= c 】然后拿总数 A*B 减去它就是最终的答案,但是我数位dp枚举的是0-A,0-B的合法数目,而题目要求的是1-A,1-B的答案,所以相当于我多减去非法答案,那么最终答案就要加上当a等于0,b等于0的合法贡献。那么,我们怎么求出满足反面的总数呢?首先把 A,B化成二进制,存到数组a,b中,设dp方程为 dp【pos】【sta1】【sta2】【limit1】【limit2】分别代表,枚举到第pos位。sta1 = 0 表示当前的(x & y) = c,1表示(x & y) < c,sta2 = 0表示当前的(x ^ y) = c,1表示(x ^ y) > c 。如果枚举到第pos位,他的sta1 = 0 && (x&y)> c,那么就continue,如果它的sta1=1,或者当前位置(x & y < c ),那么sta1=1;同理sta2。

时间复杂度:状态数*转移数=32*2*2*2*2*2。(只有0和1两种状态)。

#include<bits/stdc++.h>
using namespace std;
long long d[32][2][2][2][2];
int a[32],b[32];
int C;
long long dfs(int pos,int sta1,int sta2,int limit1,int limit2)
{
    if(pos==0)return 1;
    if(d[pos][sta1][sta2][limit1][limit2]!=-1)
        return d[pos][sta1][sta2][limit1][limit2];
 
    int up1=limit1?a[pos]:1;
    int up2=limit2?b[pos]:1;
 
    long long ans=0;
 
    for(int i=0;i<=up1;i++)
        for(int j=0;j<=up2;j++)
        {
            int x=0;
            if((C >> (pos-1)) & 1)x=1;
 
            if(((i&j)>x) && sta1==0)continue;
            if(((i^j)<x) && sta2==0)continue;
 
            int t1= sta1 || (i&j)<x;
            int t2= sta2 || (i^j)>x;
 
            ans+=dfs(pos-1,t1,t2,limit1&&(i==up1),limit2&&(j==up2));
        }
        return d[pos][sta1][sta2][limit1][limit2]=ans;
}
int main()
{
    int T;
    cin>>T;
    int x,y;
    while(T--)
    {
        memset(d,-1,sizeof(d));
        for(int i=0;i<=30;i++)a[i]=b[i]=0;
 
        scanf("%d%d%d",&x,&y,&C);
        long long ans = 1ll*x*y + max(x-C+1,0) + max(y-C+1,0);
        int t=0; 
        while(x) //求出 x 的数位
        {
            a[++t]=x%2;
            x/=2;
        }
        t=0;
        while(y) //求出 y 的数位
        {
            b[++t]=y%2;
            y/=2;
        }
 
        ans-=dfs(30,0,0,1,1); 
        printf("%lld\n",ans);
    }
}

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值