小卡与质数2
题目地址
https://www.luogu.com.cn/problem/T216909?contestId=58544题目背景
题目描述
小卡有 T T T 次询问,每次给你一个数字 x x x,问有多少个比 x x x 小的非负整数 y y y,使得 x ⊕ y x\oplus y x⊕y 是质数,其中 ⊕ \oplus ⊕ 表示按位异或。
输入输出格式
输入格式
接下来 T T T 行,每行一个正整数 x ( 1 ≤ x ≤ 1 0 6 ) x(1\le x\le 10^6) x(1≤x≤106)。
输出格式
输入输出样例
输入样例 #1
9
5
6
7
8
9
10
100
1000
10000
输出样例 #1
2
4
4
2
2
4
22
163
1132
思路:
x
⊕
y
x\oplus y
x⊕y是质数,
y
<
x
y< x
y<x,那么假设x的二进制位为1010,如果y在x二进制为1的位上为0,那么y就一定小于x,即y的二进制为0***,100*,表示任意数字。 1010和0**异或后的数是质数,大小范围一定在
[
8
,
15
]
[8, 15]
[8,15]之间,那么就看8-15之间有多少质数;1010和100异或后,质数大小范围一定在[2, 3], 那么看2-3之间有多少质数。
因此我们可以发现x的二进制有k位,第i位为1,那么y在前i-1位和x保持一致,第i位为0,后续数字任意排列,都会比x小;这样
x
⊕
y
x\oplus y
x⊕y的质数一定在
[
2
i
,
2
i
∗
2
−
1
]
[2^i, 2^i *2 - 1]
[2i,2i∗2−1]
算法实现
首先预处理出质数,由于异或后的数字可能比x大, 但总会小于 2 x 2x 2x,所以预处理的质数范围设定在2000000,之后利用前缀和再预处理出1-2000000之间有多少质数。对于每个询问t,分别求出x的二进制位,如果第i位为1,那么必然存在小于x的数能够异或出 [ 2 i , 2 i ∗ 2 − 1 ] [2^i, 2^i *2 - 1] [2i,2i∗2−1]之间的质数,因此利用前缀和查询对应区间质数的数量。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6 + 10;
int primes[N], cnt;
bool st[N];
int sum[N];
void get_primes(int n) // 线性筛质数
{
for(int i = 2; i <= n; i ++)
{
if(!st[i]) primes[cnt ++] = i;
for(int j = 0; i * primes[j] <= n; j ++)
{
st[i * primes[j]] = true;
if(i % primes[j] == 0) break;
}
}
}
int main()
{
get_primes(2000000); // 预处理质数
for(int i = 2; i <= 2000000; i ++)
{
if(!st[i]) sum[i] = sum[i - 1] + 1;
else sum[i] = sum[i - 1];
}
int t, x;
scanf("%d", &t);
while(t --)
{
scanf("%d", &x);
int ans = 0;
for(int i = 31; i >= 0; i --) // 枚举第i位是否为1
{
if(x >> i & 1)
{
int a = 1 << i, b = (1 << (i + 1)) - 1;
ans += sum[b] - sum[a - 1]; // 查询[2 ** i, 2 ** (i + 1) - 1]区间有多少个质数
}
}
printf("%d\n", ans);
}
return 0;
}