【CCPC】2022威海站 G. Grade 2 | 数学、循环节

230 阅读1分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

【CCPC】2022威海站 G. Grade 2 | 数学、循环节

题目链接

Problem - G - Codeforces

题目

image.png

题目大意

对于给定的正整数 xx,有 nn 组询问,每组询问要求对于给定的 llrr 回答:

k=lr[gcd(kxx,x)=1]\sum_{k=l}^r[gcd(kx\oplus x,x)=1]

即求有多少个正整数 kk 满足 lkrl\le k\le r,且 k×xk\times xxx 进行异或运算后,取得的结果与 xx 的最大公约数是 11

思路

显然

k=lr[gcd(kxx,x)=1]\sum_{k=l}^r[gcd(kx\oplus x,x)=1]

可以转化为

k=0r[gcd(kxx,x)=1]k=0l[gcd(kxx,x)=1]\sum_{k=0}^r[gcd(kx\oplus x,x)=1]-\sum_{k=0}^l[gcd(kx\oplus x,x)=1]

solve(R)solve(R) 表示 k=0R[gcd(kxx,x)=1]\sum_{k=0}^R[gcd(kx\oplus x,x)=1],考虑怎样求 solve(R)solve(R)

因为 ab=a+b(a&b)a\oplus b=a+b-(a\&b),而 xkxx\mid kx,所以 gcd(kxx,x)=gcd(kx&x,x)gcd(kx\oplus x,x)=gcd(kx\&x,x)。这说明 gcd(kxx,x)gcd(kx\oplus x,x) 的值只与 kxkx 的后 log2(x)\lceil log_2(x)\rceil 个二进制位有关。即

gcd(kxx,x)=gcd((kx)mod2log2(x)x,x)=gcd((kmod2log2(x)×xmod2log2(x))x,x)=gcd((kmod2log2(x)×x)x,x)\begin{aligned} gcd(kx\oplus x,x)&=gcd((kx)\mod 2^{\lceil log_2(x)\rceil}\oplus x,x)\\ &=gcd((k\mod 2^{\lceil log_2(x)\rceil}\times x\mod 2^{\lceil log_2(x)\rceil})\oplus x,x)\\ &=gcd((k\mod 2^{\lceil log_2(x)\rceil}\times x)\oplus x,x)\\ \end{aligned}

所以令 f(k)f(k) 表示 gcd(kxx,x)gcd(kx\oplus x,x),则 f(k)f(k) 具有循环节,为 2log2(x)2^{\lceil log_2(x)\rceil}

我们只需要先暴力的求出一个循环节内 f(k)=1f(k)=1kk 的个数,并对于满足 1j2log2(x)1\le j\le 2^{\lceil log_2(x)\rceil} 的正整数 jj 预处理出 11jj 里合法的 kk 的数量为 gdjgd_j,就可以 O(1)O(1) 的计算

solve(R)=R2log2(x)×gd2log2(x)+gdRmod2log2(x)solve(R)=\lfloor\frac{R}{2^{\lceil log_2(x)\rceil}}\rfloor\times gd_{2^{\lceil log_2(x)\rceil}}+gd_{R\mod 2^{\lceil log_2(x)\rceil}}

对于给定的 llrr 输出 solve(r)solve(l1)solve(r)-solve(l-1) 即可。

代码

#include <algorithm>
#include <stdio.h>
using namespace std;
const int N=1048577;
using LL=long long;
int n,x,cnt;
int gd[N];
int gcd(int a,int b)
{
	return b==0?a:gcd(b,a%b);
}
LL solve(LL R)
{
	return R/cnt*gd[cnt]+gd[R%cnt];
}
int main()
{
	scanf("%d%d",&x,&n);
	for (int i=x;i;i>>=1) cnt++;
	cnt=1<<cnt;
	for (LL i=1;i<=cnt;++i)
		gd[i]=gd[i-1]+(gcd(i*x&x,x)==1);
	while (n--)
	{
		LL l,r;
		scanf("%lld%lld",&l,&r);
		printf("%lld\n",solve(r)-solve(l-1));
	}
	return 0;
}