题目
地址:https://ac.nowcoder.com/acm/contest/9925/C
题目大意
寻找对于任意二元组 ( 0 ∼ x , 0 ∼ y ) (0\sim x, 0\sim y) (0∼x,0∼y),除了 ( 0 , 0 ) (0, 0) (0,0),并且满足 i & j = 0 i \& j=0 i&j=0的所有 l o g 2 ( i + j ) + 1 log_2(i + j) +1 log2(i+j)+1向下取整的和相加的最终结果。
思路
- 考虑到x,y的范围到达了1e9,还有t的1e5,一次需要在1e3时间内解决,无法通过枚举解决。那么类似的问题就剩两种可能性较大的解法,数学得到式子能够直接解决问题,第二种数位dp。
- 熟悉数位dp的话应该很快能看出来数位dp,由于要求 i & j = 0 i \& j=0 i&j=0,枚举数位的时候按照二进制枚举,i+j最高位永远不会超过进位,那么每一对值贡献的结果就是最高位的数位,那么直接数位dp就行了。
- 数位dp的话有一个小变形,常规的数位dp枚举一个数,这里枚举两个数,那么要通过两重for循环分别枚举两个数的同一位置的值。
- 设置状态时,如果设置的是一个数的最高位,那么会超时。。。
需要小优化,设置是否存在最高位就可以了,倒时候在转移时,如果刚好出现最高位,直接将接下来的答案乘最高位即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
int x[40], y[40];
long long dp[40][2][2][2];
long long res;
long long dfs(int pos, bool l1, bool l2, bool fs){
if (pos == -1) return 1;
if (dp[pos][l1][l2][fs] != -1) return dp[pos][l1][l2][fs];
int up1 = l1 ? x[pos] : 1;
int up2 = l2 ? y[pos] : 1;
long long ans = 0;
for (int i = 0; i <= up1; i ++){
for (int j = 0; j <= up2; j ++){
if (i & j) continue;
int fval = 1;
bool f = 0; //如果出现了下面情况,之后递归fs取1
if ((i || j) && fs == 0) fval = pos + 1, f = 1;;
ans = (ans + dfs(pos - 1, l1 && i == up1, l2 && j == up2, fs || f) * fval) % mod;
}
}
if (dp[pos][l1][l2][fs] == -1) dp[pos][l1][l2][fs] = ans;
return ans;
}
void solve(int a, int b){
int idx1 = 0, idx2 = 0;
while (a){
x[idx1 ++] = a % 2;
a /= 2;
}
while (b){
y[idx2 ++] = b % 2;
b /= 2;
}
res = dfs(max(idx1, idx2) - 1, 1, 1, 0) % mod;
}
int main()
{
int t; cin >> t;
while (t --){
memset(dp, -1, sizeof dp);
memset(x, 0, sizeof x);
memset(y, 0, sizeof y);
int a, b; cin >> a >> b;
solve(a, b);
cout << (res - 1 + mod) % mod << endl; //减去(0,0)
}
return 0;
}
}