若 ( x , y ) (x,y) (x,y)满足 x & y = 0 x\&y=0 x&y=0那么可以被计算贡献
贡献为二进制位的最高位的位数
那么容易想到定义 f [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] f[i][j][0/1][0/1] f[i][j][0/1][0/1]表示枚举到第 i i i位,最高位二进制 1 1 1是 j j j位置
x x x卡不卡上界, y y y卡不卡上界
这样乍一看是非常优秀的复杂度,然而实际上算一下发现极限数据是
3 0 2 ∗ 2 2 ∗ 1 0 5 = 3.6 ∗ 1 0 8 30^2*2^2*10^5=3.6*10^8 302∗22∗105=3.6∗108
而且数位 d p dp dp的复杂度是带常数的,会被卡死
所以需要优化
发现当我们在第 x x x位放置了 1 1 1,那么其实没必要枚举下去,我们只需要知道接下来有多少种方法填数就好了!!!
所以定义 f [ i ] [ 0 / 1 ] [ 0 / 1 ] f[i][0/1][0/1] f[i][0/1][0/1]表示枚举到二进制第 i i i位, x , y x,y x,y卡不卡上界的数对方案
我们就可以枚举最高位的 1 1 1在哪里,然后从那个位置开始数位 d p dp dp
复杂度变成 30 ∗ 2 2 ∗ 1 0 5 = 1.2 ∗ 1 0 7 30*2^2*10^5=1.2*10^7 30∗22∗105=1.2∗107
由于变成了 k k k次 d f s dfs dfs,常数略大,但因为状态固定,所以大部分时间是直接返回的
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
int t,x,y,a[33],b[33];
int f[33][2][2];
int dfs(int l,int lim1,int lim2)//h1表示最高位1的位置,h2表示是否有低位1
{
if( f[l][lim1][lim2]!=-1 ) return f[l][lim1][lim2];
if( l==0 ) return f[l][lim1][lim2] = 1;
int ed1 = lim1?a[l]:1, ed2 = lim2?b[l]:1;
long long ans = 0;
for(int i=0;i<=ed1;i++)
for(int j=0;j<=ed2;j++)
{
if( (i&j)!=0 ) continue;
ans += dfs(l-1,lim1&&(i==ed1),lim2&&(j==ed2) );
}
return f[l][lim1][lim2] = ans%mod;
}
//f[i][0/1][0/1]表示最高位1在i位置,当前卡不卡上界
int solve(int x,int y)
{
for(int i=1;i<=a[0];i++) a[i] = 0;
for(int i=1;i<=b[0];i++) b[i] = 0;
a[0] = b[0] = 0;
while( x ){ a[++a[0]] = x&1; x>>=1; }
while( y ){ b[++b[0]] = y&1; y>>=1; }
int mx = max( a[0],b[0] );
for(int i=0;i<=mx;i++)
f[i][0][0] = f[i][0][1] = f[i][1][0] = f[i][1][1] = -1;
long long ans = 0;
for(int i=1;i<=a[0];i++)//枚举最高位,x是1,y是0
ans += 1ll*dfs( i-1,i==a[0],i>b[0] )*i;
for(int i=1;i<=b[0];i++)//枚举次高位,x是0,y是1
ans += 1ll*dfs( i-1,i>a[0],i==b[0] )*i;
return ans%mod;
}
signed main()
{
cin >> t;
while( t-- )
{
scanf("%d%d",&x,&y);
printf("%d\n",solve(x,y) );
}
}
当然,更好的写法是直接一遍 d f s dfs dfs统计答案
虽然复杂度完全没区别,但是要简短一点点
比如这里写的LINK