Codeforces Round 911 (Div. 2)D.Small GCD gcd是慢滴,自己枚举去重

Problem - D - Codeforces

本题gcd暴力做会超时,因为给你一堆质数,你疯狂gcd,疯狂的递归辗转相除法,时间会超。

(三个数本题设为x y z)

如果暴力尝试过,也大概是排序,然后求和 gcd(x,y) * 后面的数的数目。

所以思路肯定是算前两个数。

目录

统计x,y同约数出现的下标:

计算x,y同约数时,f(x,y,z)为这个约数的情况数:

对情况数进行容斥去重:

我们要求的是gcd之和,乘以这个gcd:

代码:


————

本题解是总结:Codeforces Round 911 (Div. 2)(A-E)思路讲解_哔哩哔哩_bilibili

浅显易懂滴讲解: 

首先,本题数据范围是1~1e5,我们可以枚举出每个数的所有约数。(gcd不就是最大公divisor嘛)

(方法:枚举每个数,给它的所有倍数push_back自己即可。)

谁的约数有2: 2 4 6 8 10 ...

这样我们任何一个数的所有约数都统计好了。

统计x,y同约数出现的下标:

然后遍历输入的数组,每个数的所有约数都记录这个下标。然而我们不记录下标,记录n-1-i,其实是后面的数的数目,因为下标也是用来算这个的。

如何记录呢,起一个1~1e5的vector,push_back即可。这个操作还能让我们知道这个下标是第几次出现。

比如约数是3的:

                        3 3         3   3    3

  第几次:       1 2          3   4    5

计算x,y同约数时,f(x,y,z)为这个约数的情况数:

接着就是计算了,比如3这个约数出现两次,说明两个数的公约数有3(我没说是最大公约数),我们另起一个数组ans,统计f(x,y,z)为3的情况数。这个答案是后面的数的数目乘以前面出现3的次数

比如约数是3的:

                        3 3         3   3    3

  第几次:       1 2          3   4    5

                                      ^

                                 这个是第三次,可以和前面组成两种,然后乘以后面的数的数目就是总的情况数,给ans[3]加上。        

对情况数进行容斥去重:

比如:

    //            3    6 18
    //            3会出现3次
    //            6只有两次
    //            6和18的gcd是6不是3,但是我们把3多算一遍
    //            所以3要减去6的情况数

我们要求的是gcd之和,乘以这个gcd:

ret +=  ans [ i ] * i;

代码:

#define int long long
const ll inf = 1e9;
const ll mx = 1e5;

//3  3     3    3
//

vector divi[mx+2];//divisor//统计出范围内的每个数的约数
//用的时候调啊
void solve()
{
	int n;
	cin >> n;
	vectorarr(n);
	for (int i = 0; i < n; i++)
	{
		cin >> arr[i];
	}
	sort(arr.begin(), arr.end());
	vectordarr[mx + 2];
	for (int i = 0; i < n; i++)
	{
		//每个数的gcd后面的情况数
		for (auto x : divi[arr[i]])
		{
			darr[x].push_back(n - 1 - i);
		}
	}
	vectorans(mx + 2);
	for (int i = 1; i <= mx; i++)
	{
		int cnt = 0;
		//第二个数开始才会加(共同gcd)
		//cnt统计前面出现的次数
		for (auto x : darr[i])
		{
			ans[i] += cnt * x;
			cnt++;
		}
	}
	//去重——容斥

	for (int i = mx; i >= 1; i--)
	{
		for (int j = 2 * i; j <= mx; j += i)
		{
			ans[i] -= ans[j];
		}
	}
	ll ret = 0;
	for (int i = 1; i <= mx; i++)
	{
		ret += ans[i]*i;//前面求的是情况数,答案是所有gcd之和
	}
	cout << ret << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	for (int i = 1; i <= mx; i++)
	{
		for (int j = i; j <= mx; j += i)//我是我倍数的约数
		{
			divi[j].push_back(i);
		}
	}
	int t = 1;
	cin >> t;
	for (int i = 1; i <= t; i++)
	{
		solve();
	}
	return 0;
}

你可能感兴趣的:(CF,算法,数学)