题意:
计算 ∑1≤i≤n∑1≤j≤mgcd(i AND j,i OR j)
1≤t≤3,1≤n,m≤15000
解释:
a=i AND j,b=i OR j
貌似暴力能过,记录一下出现过的a, b就好了!
还是说一下一般的做法吧!
记 l=log2maxn,m枚举 a 和
b 的值分别是多少,因为是有包含关系的,所以枚举量是$3^l的,用枚举子集的方法。然后要计算有多少组 i,j 满足条件, 且 1≤i≤n,1≤j≤m , i 和
j 肯定都包含了 a ,剩下b−a 的那些bit要分配给 i 和j ,你可以算出最大分配多少才能使 i 不超过n , j 也是同理,于是就确定了一个区间,区间内的都是合法的方案。所以总的复杂度就是
O(l×3l)=O(n1.59logn)
这种方法速度上相比暴力也就快了那么一点点!
代码:
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
int n,m,L;
vector<int> A[1<<16];
long long ans=0;
int get(int k1,int k2)
{
int l = 0, r = A[k1].size(), ans = -1;
while (l < r){
int mid = (l + r) >> 1;
if (A[k1][mid] <= k2) ans = mid, l = mid + 1;
else r = mid;
}
return ans + 1;
}
int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b); }
// pos 当前处于二进制的 pos 位
// 枚举 a = &, b = |
void getans(int pos,int a,int b)
{
if(pos == L) {
int sum = b - a, R = n - a,L = b - m;
if(a == 0) {
R = min(R, sum-1), L = max(L, 1);
}
if(L > R) return ;
// L R 分别表示 a 能填的数字的范围
int cnt = gcd(a, b);
ans = (ans + 1ll * cnt * (get(sum, R) - get(sum, L - 1)));
return;
}
getans(pos + 1, a, b);
getans(pos + 1, a, b + (1<<pos));
getans(pos + 1, a + (1<<pos), b + (1<<pos));
}
void solve()
{
scanf("%d%d", &n, &m);
if (n < m) swap(n, m);
ans = 0, L = 0;
while ((1 << L) <= n) L ++;
// 改为全局的预处理会快些
for (int i = 0;i < (1<<L);i ++){
A[i].clear();
// 枚举 i 对应二进制位上的所有子集
// i = 101
// j = 101, 100, 001, 000
for (int j = i;;j = (j - 1) & i){
A[i].push_back(j);
if (j == 0) break;
}
sort(A[i].begin(), A[i].end());
}
getans(0, 0, 0);
printf("%I64d\n", ans);
}
int main()
{
//freopen("in.txt", "r", stdin);
int T;
scanf("%d",&T);
while(T--)
solve();
return 0;
}