例21-8 删数

题目:给定(n)个整数。对于其中的每个数(a[i]),求出删去它以后剩下的所有数的最大公约数,(n<=10^6)

对于删去(a[i])后的数组,显然剩下的数一定是(a[1])(a[i-1])(前缀)和(a[i+1])(a[n])(后缀)。这意味着,如果用(Left[i])表示(a[1])(a[i])的最大公约数,(Right[i])表示(a[i])(a[n])的最大公约数,那么删除(a[i])以后的答案就是((Left[i-1],Right[i+1])),可以快速求出。

1、以空间换时间,算完的结果保存下来,下次可以复用。

2、最大公约数有这样的性质,可以几个人先算,算完的就有用,可以接着算。

3、左端数组,需要从左向右算。右端数组,需要从右向左算。为什么这样呢?举个栗子,自已想想原因。

#include <bits/stdc++.h>

using namespace std;
const int N = 110;
int a[N];
int Left[N], Right[N];

//最大公约数,辗转相除法
int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

/**
 * 测试数据:
 5
 12 36 24 18 48
 结果:6 6 6 12 6
 */

int main() {
    int n;
    cin >> n;
    //读入数据
    for (int i = 1; i <= n; i++) cin >> a[i];
    //类似于递推式获取前1~(i-1)的最大公约数记录到Left[i]中。
    for (int i = 1; i <= n; i++) Left[i] = gcd(Left[i - 1], a[i]);
    //类似于递推式获取后n~(i+1)的最大公约数记录到Right[i]中。
    for (int i = n; i >= 1; i--) Right[i] = gcd(a[i], Right[i + 1]);
    //根据性质3,可以通过左边最大公约和右边最大公约计算出去掉当前数字的最大公约
    for (int i = 1; i <= n; i++) cout << gcd(Left[i - 1], Right[i + 1]) << " ";
    //时间分析:O(3*n)
    //如果去掉每一个,分别进行重新计算的话,外层循环贡献n,内层还需要一层遍历所有剩余数字,可以认为是O(N^2)级。
    //这样通过性质3,就可以实现平方级别时间复杂度到线性复杂度的进化。
    //究其原因,就是空间换时间,把已知的结果保存下来,避开了重复的计算。
    return 0;
}