题解:P15608 [ICPC 2021 Jakarta R] Not One
前言
肯定是因为我太菜了)这道题卡了我两天,发个题解纪念一下。
题目大意
给你一棵树,每个点有权值
乍一看好像是一道树形 dp,然后你就会喜提第六个测试点 Wrong Answer。
聪明的你拿起放大镜仔细观察,发现这道题有一些特殊的东东:若一棵子树每个节点的最大公约数大于一,那么,一定存在一个质数
那么!问题就可以转化为:对于每个质数
解题思路
- 用线性筛预处理出
10^6 以内所有的质数(这很简单就不讲了)。 - 对于每个节点的权值
a_i ,分解其质因子(这也很简单应该不用细讲吧)。然后建立一个映射关系:每个质数p 与所有权值拥有质因子p 的节点形成映射。 - 对于每个质数
p ,按照前面的想法,在树上只看权值的质因子包含p 的节点,求这些点形成的子图(其实是森林)中最大联通块的大小。注意! 这里使用朴素的遍历会喜提绿黑大礼包(AC 和 TLE),所以我们需要采用时间戳染色进行优化,这个不会的建议先学一下这道题。我们遍历所有质数p 的映射表,遇到未访问的节点就进行 bfs 搜索,对于遍历到的每个节点u ,只走到与之相邻且一样权值含有质因子p 的节点,并统计联通块大小,并打擂台取最大值。
时间复杂度为
其余步骤详见代码及注释。
代码
终于到你们最爱看的代码了。。。马蜂有点猎奇请见谅!
/*
Author: Enoch2013
Time: 2026.7.22 10:21:09 (a.m.)
Using Windows10 at Quanzhou,Fujian Province
*/
#include <bits/stdc++.h>
#define pb push_back
#define rep(x, y, z) for (int x = (y); x <= (z); x++)
#define per(x, y, z) for (int x = (y); x >= (z); x--)
#define mem(a, b) memset((a), (b), sizeof (a))
using namespace std;
using vi = vector<int>;
// 快读快写不用说了。
inline int read()
{
int ans = 0;
char ch = getchar();
while (!isdigit(ch))
ch = getchar();
while (isdigit(ch))
ans = (ans << 3) + (ans << 1) + (ch ^ 48), ch = getchar();
return ans;
}
inline void write(int x)
{
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 1e6 + 10;
bool is_pr[N]; // 标记是否为质数
// a[] 为输入的权值,bfn[] 为访问标记数组(配合时间戳使用)。
// 当 bfsn[u] == cnt 时表示节点 u 为当前质数 p 的可选节点,cnt 记录时间戳,lmao 存储映射关系,详见上文。
int n = read(), a[N], bfn[N], bfsn[N], cnt, ans; vi g[N], lmao[N], prime; inline void add(int u, int v) { g[u].pb(v), g[v].pb(u); } // 双向建边
inline void init() // 线性筛不用讲了吧。。。
{
mem(is_pr, 1), is_pr[0] ^= 1, is_pr[1] ^= 1;
rep(i, 2, N - 10)
{
if (is_pr[i]) prime.pb(i);
for (auto &qp : prime) // qp!(bushi
{
if (qp > (N - 10) / i) break;
is_pr[i * qp] ^= 1;
if (!(i % qp)) break;
}
}
}
inline vi get(int x) // 分解质因子应该也不用讲了吧。。。
{
vi res;
if (x == 1) return res;
for (auto &qp : prime) // qp * 2
{
if (qp > x / qp) break;
if (!(x % qp)) { res.pb(qp); while (!(x % qp)) x /= qp; }
}
if (x > 1) res.pb(x);
return res;
}
inline void bfs(int S) // 利用广搜求联通块大小(不用 dfs 是因为怕爆栈
{
queue<int> q; q.push(S); bfn[S] = cnt; // 标记访问
int sz = 0; // 该联通块的大小
while (q.size())
{
int u = q.front(); q.pop(); sz++;
for (auto &to : g[u]) if (bfn[to] != cnt && bfsn[to] == cnt) bfn[to] = cnt, q.push(to); // 未被本轮访问且为可选节点
}
ans = max(ans, sz); // 打擂台取最大值
}
int main()
{
init();
rep(i, 1, n)
{
a[i] = read();
for (auto &qp : get(a[i])) lmao[qp].pb(i); // 建立映射关系。qp * 3(???
}
rep(i, 1, n - 1) { int u = read(), v = read(); add(u, v); } // 建树
rep(i, 2, N - 10) // 遍历所有数
{
if (lmao[i].empty()) continue; // 没有映射关系就跳过
cnt++; // 时间戳加一
for (auto &u : lmao[i]) bfsn[u] = cnt; // 本轮可选节点
for (auto &S : lmao[i]) if (bfn[S] != cnt) bfs(S); // 遍历子图(森林)求联通块大小
}
write(ans); putchar('\n'); // 输出答案
return 0;
}
完结散花!