其他分享
首页 > 其他分享> > 2022.7.30 LeetCode AcWing

2022.7.30 LeetCode AcWing

作者:互联网

LeetCode

建图 + 并查集 + (筛法求质数)

如果暴力的建图,跑dfs最大连通图,建图会成为瓶颈,O(n^2)。

但是考虑到,A、B两数,A、B分别与其非1非自身的因数相连,如果A、B之间联通,那么A、B相互联通的那个点为公因数。可以将时间复杂度从依赖数据量的大小转移到依赖数据按数据范围。
即建图时,遍历每个数,并对其非1非自身的因子相连,则建图过程为O(n根号m反阿克曼函数(m))。(m为数据范围)而查询过称,可以用并查集实现,时间复杂度为O(n反阿克曼函数(n))。取二者较大的一个,最终时间复杂度为O(n根号m*反阿克曼函数(m))。

class UnionFind {
public:
    UnionFind(int n) {
        parent = vector<int>(n);
        rank = vector<int>(n);
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    void uni(int x, int y) {
        int rootx = find(x);
        int rooty = find(y);
        if (rootx != rooty) {
            if (rank[rootx] > rank[rooty]) {
                parent[rooty] = rootx;
            } else if (rank[rootx] < rank[rooty]) {
                parent[rootx] = rooty;
            } else {
                parent[rooty] = rootx;
                rank[rootx]++;
            }
        }
    }

    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
private:
    vector<int> parent;
    vector<int> rank;
};

class Solution {
public:
    int largestComponentSize(vector<int>& nums) {
        int m = *max_element(nums.begin(), nums.end());
        UnionFind uf(m + 1);
        for (int num : nums) {
            for (int i = 2; i * i <= num; i++) {
                if (num % i == 0) {
                    uf.uni(num, i);
                    uf.uni(num, num / i);
                }
            }
        }
        vector<int> counts(m + 1);
        int ans = 0;
        for (int num : nums) {
            int root = uf.find(num);
            counts[root]++;
            ans = max(ans, counts[root]);
        }
        return ans;
    }
};

筛法优化

从上文的分析中可以看出,建图仍然是该算法的瓶颈。

考虑到 任何一个整数数都可以唯一地分解为若干个质数之积,那么对于每一个num,没必要和它所有的因数相连,只需要和它的质因数相连即可。例如,对于数A、B,如果A、B之间的因数要么是质因数,要么是质因数的倍数。A、B的这一公因数实际可以被被更小的一个质公因数完全取代而不产生影响。

而求质数就可以考虑用 埃氏筛 OR 欧拉筛。 其时间复杂度分别为 O(n log log n) 和 O(n)。
看起来如果数据范围为m,再运行n次,时间复杂度反不如上面的,但是,筛法求质数可以只运行一次,并将结果保存起来,这样就不需要运行n次。
黎曼曾给出小于n的质数的个数,约等于 PI(n) = n / ln n。 链接在这里
这样时间复杂度为 O(mloglogm + n * PI(n) * 反阿克曼函数(PI(n)) + n * 反阿克曼函数(n)) 和 O(m + n * PI(n) * 反阿克曼函数(PI(n)) + n * 反阿克曼函数(n))。
无限接近于O(m)。

欧拉筛的写法如下:

class Solution {
    public static int n = (int) 1e5 + 7;
    public static int[] isPrime = new int[n];
    public static int[] primes = new int[n];
    //并查集 采用路径压缩
    public static int[] parent = new int[n];
    int k = 0;

    public int largestComponentSize(int[] nums) {
        //欧拉筛,找出1-n的所有质数
        for (int i = 2; i < n; i++) {
            if (isPrime[i] == 0) {
                primes[k++] = i;
            }
            for (int j = 0; primes[j] * i <= n; j++) {
                isPrime[primes[j] * i] = 1;
                if (i % primes[j] == 0) {
                    break;
                }
            }
        }
        //初始化并查集
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
        //遍历nums中的每个数,和他们的质因数连接
        for (int num : nums) {
            int quotient = num;
            for (int j = 0; j < k && primes[j] * primes[j] <= quotient; j++) {
                if (quotient % primes[j] == 0) {
                    //primes[i]是他的质因数
                    union(num, primes[j]);
                    while (quotient % primes[j] == 0) {
                        quotient /= primes[j];
                    }
                }
            }
            //假如剩下了一个质因数,也和num连接,使得不同的质因数可以联合到一起
            //这种情况是因为 num是一个合数 由不同的质因数相乘组成 把他的质因数 连接起来
            if (quotient > 1) {
                union(quotient, num);
            }
        }
        int[] cnt = new int[n];
        int ans = 0;
        //是否属于某个根
        for (int num : nums) {
            ans = Math.max(ans, ++cnt[find(num)]);
        }
        return ans;
    }

    public void union(int x, int y) {
        int parentX = find(x);
        int parentY = find(y);

        if (parentX != parentY) {
            parent[parentX] = parentY;
        }
    }

    public int find(int x) {
        return parent[x] == x ? x : (parent[x] = find(parent[x]));
    }
}

另附上埃氏筛和欧拉筛:

    private static void erichsen() {
        int n = (int) 1e5;
        //是否是质数,1-质数 0-合数
        int[] isPrime = new int[n];
        Arrays.fill(isPrime, 1);
        //采用i < n / i 防止i*i超范围
        for (int i = 2; i < n / i; i++) {
            if (isPrime[i] == 1) {
                //删除i的倍数
                for (int j = i * i; j < n; j += i) {
                    isPrime[j] = 0;
                }
            }
        }
        int count = 0;
        for (int i = 2; i < n; i++) {
            if (isPrime[i] == 1) {
                count++;
            }
        }
        System.out.println(count);
    }

欧拉筛:

    private static void euler() {
        int n = (int) 1e5;
        //判断是否是质数
        int[] isPrime = new int[n];
        //存放质数
        int[] primes = new int[n];
        int k = 0;
        Arrays.fill(isPrime, 1);
        int count = 0;
        for (int i = 2; i < n; i++) {
            if (isPrime[i] == 1) {
                primes[k++] = i;
                count++;
            }
            for (int j = 0; primes[j] * i < n; j++) {
                //每个质数都和i相乘 得到合数
                isPrime[primes[j] * i] = 0;
                if (i % primes[j] == 0) {
                    break;
                }
            }
        }
        System.out.println(count);
    }

AcWing

简单模拟

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

int T;
int n;
int v[N];
int ans;

int main() {
	scanf("%d", &T);
	for (int ct = 1; ct <= T; ct++) {
		ans = 0;
		int maxn = 0;

		scanf("%d", &n);

		for (int i = 1; i <= n; i++)
			scanf("%d", &v[i]);

		for (int i = 1; i <= n; i++) {
			if (v[i] > maxn && (i == n || v[i] > v[i + 1])) {
				ans++;
			}
			maxn = max(maxn, v[i]);
		}

		printf("Case #%d: %d\n", ct, ans);
	}


	return 0;
}

标签:parent,int,质数,30,++,ans,2022.7,isPrime,LeetCode
来源: https://www.cnblogs.com/superPG/p/16534364.html