Codeforces Round #694 (Div. 1) - B. Strange Definition

数论

Problem - B - Codeforces

题意

给定 \(n\;(1<=n<=3*10^5)\) 个数 \(a[i]\), \(1<=a[i]<=10^6\)

\(a[i]\) 看做是 n 个点的点权

如果 \(\frac {lcm(a[i],a[j])}{gcd(a[i],a[j])}\) 是完全平方数,则可以给 \(i\lrarr j\) 连一条无向边

每秒后都会令每个连通块中的每个点的点权变为整个连通块的点权乘积,有 \(q\;(1<=q<=3*10^5)\) 次询问,每次回答 \(w\) 秒后最大的连通块大小

思路

  1. \(\frac {lcm(a[i],a[j])}{gcd(a[i],a[j])}\) 是完全平方数 \(\Lrarr\) \(a[i]*a[j]\) 是完全平方数

  2. \(a[i]=p_1^{s_1}*p_2^{s_2}*...p_n^{s_n}\), 我们不关心 \(s_i\) 具体是多少,只需要关心 \(s_i\) 的奇偶性,因此令 \(y=p_1^{s_1mod\;2}*p_2^{s_2mod 2}*...p_n^{s_nmod\;2}\)

  3. 初始时某个连通块有 3 种情况

    1. 每个点点权的 y 都为 1
    2. 每个点点权的 y 不为 1,连通块的大小是偶数
    3. 每个点点权的 y 不为 1,连通块的大小是奇数
  4. w = 0 时,求连通块大小的最大值即可;

    w >= 1 时,第 2 种连通块会转换为 1,第 3 种永远是第 3 种,因此把 2 转换成 1,再求 max(第 1 种的大小,第 3 种的大小的最大值)

  5. 实际上不需要建边跑并查集,复杂度太高;

    用 y 来表示即可,枚举 \(a[i]\), 分解 \(a[i]\) 求出对应的 y,\(mp[y]++\);

    \(mp[1]\) 就是第一种连通块的大小;其余的 y 也可用 \(mp[y]\mod2\) 来判断其大小的奇偶性,详细见代码

  6. 第 2 步的分解有两种方法:

    1. 线性筛 \(\sqrt{max(a[i])}\) 内的素数,用素数来分解,复杂度为线性筛的 \(O(1000)\), 分解单个数的 \(O(\frac {\sqrt {a[i]}}{\ln{\sqrt{a[i]}}})\)
    2. 线性筛 \(max(a[i])\) 内的素数,并在线性筛中预处理除 \(p[i]\) 表示 \(i\) 的最小质因子为 \(p[i]\);分解时每次就可以精准找到下一个要除的素数,复杂度为 线性筛的 \(O(10^6)\), 分解单个数的 \(O(\log {a[i]})\)

    当值域较小且要分解的数较多时用第 2 种更好

代码

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"

typedef long long ll;
typedef pair<int, int> PII;

const int N = 3e5 + 10, M = 1e6 + 10;
int n;
int a[N];
int pr[M / 5], p[M], cnt;
void get_primes(int n)
{
	p[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		if (!p[i])
		{
			pr[++cnt] = i;
			p[i] = i;
		}
			
		for (int j = 1; j <= cnt && pr[j] <= n / i; j++)
		{
			p[i * pr[j]] = pr[j];
			if (p[i] == pr[j])
				break;
		}
	}
}
map<int, int> mp;
void fenjie(int x)
{
	int y = 1;
	while(x > 1)
	{
		int k = 0;
		int minp = p[x];
		while(x % minp == 0)
		{
			k++;
			x /= minp;
		}
		if (k & 1)
			y *= minp;
	}
	mp[y]++;
}
int main()
{
	int T;
	scanf("%d", &T);
	get_primes(M - 10);
	while(T--)
	{
		scanf("%d", &n);
		mp.clear();
		for (int i = 1; i <= n; i++)
		{
			int x;
			scanf("%d", &x);
			fenjie(x);
		}
		int ans0 = 0;
        //w = 0
		for (auto &[y, t] : mp)
			ans0 = max(ans0, t);
        //w >= 1
		int ans1 = 0;
		for (auto &[y, t] : mp)
		{
            //把第 2 种变成第 1 种
			if (y != 1 && t % 2 == 0)
			{
				mp[1] += t;
				mp[y] = 0;
			}
		}
		for (auto &[y, t] : mp)
			ans1 = max(ans1, t);
		int q;
		scanf("%d", &q);
		while(q--)
		{
			int w;
			scanf("%d", &w);
			if (w >= 1)
				printf("%d\n", ans1);
			else
				printf("%d\n", ans0);
		}
	}
    return 0;
}
posted @ 2022-10-09 23:54  hzy0227  阅读(21)  评论(0编辑  收藏  举报