链接:https://ac.nowcoder.com/acm/contest/887/H
题意:T组样例。每组样例给出A、B、C三个数,从[1,A]选出一个数x、从[1,B]选出一个数y,使得x&y>C或x^y
思路:纯暴力的方法就是枚举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
#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&&xdigitc[len])||yihuo==2)) continue;
//确定数完这一位后的状态
if(!yu)
{
if(x>digitc[len]) nyu=1;
else if(xdigitc[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的情况。
}
}