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

链接:https://ac.nowcoder.com/acm/contest/887/H

题意:T组样例。每组样例给出A、B、C三个数,从[1,A]选出一个数x、从[1,B]选出一个数y,使得x&y>C或x^y<C。问这样的数对有多少个?

思路:纯暴力的方法就是枚举x和y,统计答案。也就是x从1数到A,y再从1数到B,统计答案。这不就是数数吗?而快速数数的算法不就是数位dp吗?(PS:我太菜了,比赛时一丁点没想到数位dp。)由于&和^运算获得值的大小都是高位优先决定,而数位dp又是从高位向低位开始数,这不是显然就用数位dp吗?首先x和y肯定要一起数(这样才方便比较大小嘛。),每次数的时候我们要记录一些状态。limita、limitb分别表示数的这个数是不是上限,len表示数到哪一位。yu表示数到现在len+1位为止,进行&运算所能确定的结果,yu==0表示可能大于C,yu==1表示已经大于C,yu==2表示已经小于C;yihuo表示数到现在len+1位为止,进行^运算所能确定的结果,yihuo==0表示可能小于C,yihuo==1表示已经小于C,yihuo==2表示已经大于C。最后,因为dfs时算上了x=0或y=0的情况,要减去。具体看代码。

参考博客(神仙大佬还加了优化,相形见绌啊。。。。。。。):https://blog.csdn.net/u013534123/article/details/98877628

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[32][3][3][2][2];
int digita[32],digitb[32],digitc[32];
ll dfs(int len,int yu,int yihuo,bool limita,bool limitb)
{
	//如果&和^运算都已经不满足要求了,直接返回0 
	if(yu==2&&yihuo==2) return 0;
	//数到0位,返回是否可以。 
	if(len==0) return yu==1||yihuo==1;
	//如果这种状态数过了,不必再数,数位dp的精髓,记忆化搜索 
	if(dp[len][yu][yihuo][limita][limitb]!=-1) return dp[len][yu][yihuo][limita][limitb];
	//确定上x和y在该位的上界 
	int upa=limita?digita[len]:1,upb=limitb?digitb[len]:1;	
	ll res=0;
	for(int i=0;i<=upa;i++)
		for(int j=0;j<=upb;j++)
		{
			int x=i&j,y=i^j,nyu=yu,nyihuo=yihuo;
			//如果数的这一位&运算已经小于C并且^运算已经大于C直接跳过不数。 
			if(((!yu&&x<digitc[len])||yu==2)&&((!yihuo&&y>digitc[len])||yihuo==2)) continue;			
			//确定数完这一位后的状态 
			if(!yu)
			{
				if(x>digitc[len])  nyu=1; 
				else if(x<digitc[len]) nyu=2;
				else nyu=0;
			}
			if(!yihuo)
			{
				if(y<digitc[len])  nyihuo=1; 
				else if(y>digitc[len]) nyihuo=2;
				else nyihuo=0;
			}
			res+=dfs(len-1,nyu,nyihuo,limita&&i==upa,limitb&&j==upb);
		}
	return dp[len][yu][yihuo][limita][limitb]=res;	
}

ll cal(ll a,ll b,ll c)
{
	memset(digita,0,sizeof(digita));
	memset(digitb,0,sizeof(digitb));
	memset(digitc,0,sizeof(digitc));
	int lena=0,lenb=0,lenc=0,len;
	while(a) digita[++lena]=(a&1),a>>=1;;
	while(b) digitb[++lenb]=(b&1),b>>=1;
	while(c) digitc[++lenc]=(c&1),c>>=1;
	len=max(lena,max(lenb,lenc));
	return dfs(len,0,0,1,1);
}
int main(void)
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		ll a,b,c;
		memset(dp,-1,sizeof(dp));
		scanf("%lld%lld%lld",&a,&b,&c);
		printf("%lld\n",cal(a,b,c)-min(b,c-1)-min(a,c-1)-1);
		//减的分别是x=0&&y!=0;x!=0&&y==0;x==0&&y==0的情况。 
	}

}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值