51nod 2583 数论只会Gcd

题目描述:

有这样一段两两求最大公约数的程序CoGcd,

int Gcd(int x, int y)
{
    if(y == 0)
        return x;

    return Gcd(y, x % y);
}

void CoGcd(int m)
{
    for(int i = 1; i <= m; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            Gcd(i, j);
        }
    }
}

给出m的值,进行t次询问,每次询问包含一对xi,yi。针对每次询问,输出整个程序执行过程当中,Gcd(xi, yi)被执行了多少次。

例如:m = 20,

Gcd(8,5)会被执行4次,对应的x, y值是

(8,5) (5,8) (13,8) (8,13),这4组x y,在调用Gcd时,会递归执行Gcd(8, 5)。

 收起

输入

第一行:2个数,t和m,中间用空格分割。(1 <= t <= 200,  10 <= m <= 1e9)
后面t行:每行2个数xi, yi,中间用空格分割。(1 <= xi, yi <= 1e9)

输出

输出共t行,对应t组询问的答案。

数据范围

1 <= t <= 200,  10 <= m <= 1e9
1 <= xi, yi <= 1e9

输入样例

5 20
1 1
2 1
3 2
5 3
8 5

输出样例

1
88
36
12
4

题解:

假设询问的是(x,y),x>y(x,y),x>y。

考虑建一棵树,每个点代表一个二元组(a,b)(a>b,a≤m)(a,b)(a>b,a≤m)。如果(a,b)(a,b)进行一次操作后会变成(a′,b′)(a′,b′),那么(a′,b′)(a′,b′)是(a,b)(a,b)的父节点。要求的就是(x,y)(x,y)的子树大小×2×2。

容易发现(a,b)(a,b)的直接儿子是(a+b,a),(2a+b,a),(3a+b,a)…(a+b,a),(2a+b,a),(3a+b,a)…。如下图:

2020-01-31 11-55-40屏幕截图

考虑改变连边方式,变成这样:

2020-01-31 11-56-19屏幕截图

再将奇数层(假设(a,b)(a,b)在第0层)的点编号两维取反,变成这样:

2020-01-31 11-57-36屏幕截图

问题变成了,一开始有一个二元组(x,x+y)(x,x+y),每次可以进行以下两种操作之一:

(a,b)−>(a+b,b)(a,b)−>(a+b,b)

(a,b)−>(a,a+b)(a,b)−>(a,a+b)

问能到达多少节点。

用二元组(p,q)(p,q)表示一个数px+q(x+y)px+q(x+y),那么就是一开始有一个四元组((1,0),(0,1))((1,0),(0,1)),每次可以进行以下两种操作之一:

((a,b),(c,d))−>((a+c,b+d),(b,d))((a,b),(c,d))−>((a+c,b+d),(b,d))

((a,b),(c,d))−>((a,b),(a+c,b+d))((a,b),(c,d))−>((a,b),(a+c,b+d))

可以发现这类似于Stern-Brocot Tree的构造过程,在不考虑≤m≤m限制的前提下,SBTree上任意两个相邻节点代表一个合法的四元组。如下图:

1138635-20171020091559959-1244161674

现在有了≤m≤m的限制,一对合法四元组是由两个二元组组成的,考虑在较大的那个二元组处统计它。(较大的二元组指两维都较大的二元组,这里因为题目性质,两维都较大的说法是不存在歧义的。)根据SBTree的性质,一对二元组(i,j)(i,j)出现过当且仅当gcd(i,j)=1gcd(i,j)=1,且每个二元组会作为较大值贡献答案22次(如上图,在且仅在该二元组第一次出现的那一层,它比相邻两个二元组大)。≤m≤m的限制就是额外要求xi+(x+y)j≤mxi+(x+y)j≤m。

那么要求的子树大小就是2×∑i,j[gcd(i,j)=1][xi+(x+y)j≤m]2×∑i,j[gcd(i,j)=1][xi+(x+y)j≤m]。随便莫比乌斯反演以后杜教筛+类欧搞搞就完事了!!

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
typedef long long ll;

int gi() {
	int x=0,o=1;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if(ch=='-') o=-1,ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return x*o;
}

int mu[N],smu[N],m;
map<int,int> M;
ll ans;

int getsmu(int n) {
	if(n<N) return smu[n];
	if(M.count(n)) return M[n];
	int ret=1;
	for(int i=2,j;i<=n;i=j+1) {
		j=n/(n/i);ret-=(j-i+1)*getsmu(n/i);
	}
	return M[n]=ret;
}

ll cal(ll n,ll a,ll b,ll c) {
	if(n<0) return 0;
	if(!a) return (b/c)*(n+1);
	if(a>=c||b>=c) return cal(n,a%c,b%c,c)+n*(n+1)/2*(a/c)+(n+1)*(b/c);
	ll m=(a*n+b)/c;
	return n*m-cal(m-1,c,c-b-1,a);
}

ll cal(int x,int y,int n) {
	return cal(n/x-1,x,n%x,y);
}

int main() {
	mu[1]=1;
	for(int i=1;i<N;i++)
		for(int j=i+i;j<N;j+=i) mu[j]-=mu[i];
	for(int i=1;i<N;i++) smu[i]=smu[i-1]+mu[i];
	int T=gi();m=gi();
	while(T--) {
		int x=gi(),y=gi();
		if(x>m||y>m) cout<<"0\n";
		else if(x<=y) cout<<"1\n";
		else {
			y+=x;
			if(y>m) cout<<"2\n";
			else {
				ans=4;
				for(int i=1,j;i<=m;i=j+1) {
					j=m/(m/i);ans+=4ll*(getsmu(j)-getsmu(i-1))*cal(x,y,m/i);
				}
				cout<<ans<<'\n';
			}
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值