前置知识:
素数判断( 素数探测可用可不用吧) + + 分组思维
具体做法
观察题目,发现题目要求我们求 不等于 的一条最长链。
那么如果这条链上所有的节点的 都不为 ,那么它们肯定有着至少一个相同的质因子。
所以我们考虑将拥有相同质因子的节点放在一起处理,然后对于具有相同质因子的节点中求一条最长链就行了。
求最长链的过程用 DFS 实现即可。
于是这个题目的解决流程是这样子的:
对于所有的给出的节点的权值进行质因数分解。 有着相同质因子的数我们就放入 map 中,定义一个 <int,vector<int> > 类型的 map 即可。 然后遍历所有出现过的质因子(可以用另外一个数组记录,并且用 map 来防止重复入队) 对于有着相同质因子的所有节点各求最长链。 求出最大值即可。
这个题目的复杂度看似无法保证。但是我们可以分析一下这道题目的最坏情况下的复杂度。(用出数据的人的思维)
我们的主要目标就是要使得 中记录的节点最多。(因为对于 中的节点我们每一个都会访问一次)
那么也就是说,对于同一个数,我们总是希望它被分在不同的组里面,这样子就能使得这个程序被卡(bushi。
但是对于一个 以内的数,它最多有 个不同的质因子,所以整个 中的节点最多是 个,并且我们可以得出使得这个程序跑满的那一个权值:
于是这个题目的最坏复杂度大概就是 ,非极限情况复杂度大概是 ( 是来自 的)。
另外要提一嘴的就是,因为质因数分解的复杂度是 的,假若给定的权值全是很大的质数的话,那么这个程序就会被卡死,所以我的题解里面写了 素数探测算法,官方题解提供者:“代码长了不好看”(结果就被我 了。
Code
// Problem: 最长树链 // Memory Limit: 65536 MB // Time Limit: 2000 ms #include <bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 50; #define int long long int n,start[MAXN], data[MAXN]; int Prime[MAXN],tot = 0 ,cnt = 0, book[MAXN]; int vis[MAXN],D[MAXN * 11],dis[MAXN] , tail = 0 , q[MAXN]; int Ans = 1,Mod,A[11] = {0,2,3,5,7,11,13,17,19,23,61}; // A就是Miller Rabin 探测的底数 map <int,vector<int> > mp; map <int,int > M;//记录哪一些质因子出现过 struct Edge { int next,to; void add(int from,int To) {to = To , next = start[from], start[from] = tot;} } edge[MAXN * 2]; inline int read() { int x = 0 , flag = 1; char ch = getchar(); for( ; ch > '9' || ch < '0' ; ch = getchar()) if(ch == '-') flag = -1; for( ; ch >= '0' && ch <= '9' ; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0'; return x * flag; } void GetPrime() { for(int i = 2 ; i <= 5e4 ; i ++) { if(!book[i]) Prime[++cnt] = i; for(int j = 1 ; j <= cnt ; j ++) { if(Prime[j] * i > 5e4) break; book[Prime[j] * i] = 1; if(i % Prime[j] == 0) break; } } return ; } long long quickpower(long long x,long long y) { long long ans = 1 , op = x; while(y) { if(y & 1ll) ans *= op , ans %= Mod; op *= op , op %= Mod; y >>= 1ll; } return ans; } int Miller_Rabin(int x) {//素数探测算法 Mod = x; int s = 0; if(x == 2) return 1; if(x % 2 == 0 || x == 1) return 0; int p = x - 1 ; while(p % 2 == 0) p/= 2 , s ++; for(int j = 1 ; j <= 5 ; j ++) { long long B = quickpower(A[j] , p); for(int i = 1 ; i <= s ; i ++) { long long K = (B * B) % Mod; if(K == 1ll && B != 1ll && B != Mod - 1) return 0;//如果二次探测发现这个不是质数 B = K; } if(B != 1ll) return 0;//利用费马小定理判断 } return 1;//探测正常结束 } void DFS(int x,int P,int from) { vis[x] = 1;//处理与 x 点在同一个连通块的情况。 q[++tail] = x; dis[x] = dis[from] + 1; for(int i = start[x] ; i ; i = edge[i].next) { int to = edge[i].to; if(to == from) continue; if(data[to] % P == 0) DFS(to,P,x); } return ; } int solve(int x,int P) {//笔者采用两次DFS求直径的方法来求出最长链 tail = 0; int ans = 1; vis[x] = 1; DFS(x,P,0);//进行一次DFS求出离当前点最远的点P int f = 0; for(int i = 1 ; i <= tail ; i ++) if(dis[q[i]] > dis[f]) f = q[i];//找出离自己最远的 for(int i = 1 ; i <= tail ; i ++) dis[q[i]] = 0; tail = 0; DFS(f,P,0);//第二次DFS f = 0; for(int i = 1 ; i <= tail ; i ++) if(dis[q[i]] > dis[f]) f = q[i];//找出离P最远的点Q return dis[f];//这个连通块的最长链 } signed main() { n = read(); GetPrime(); memset(data,0,sizeof(data)); for(int i = 1 ; i <= n - 1; i ++) { int u = read() , v = read(); edge[++tot].add(u,v); edge[++tot].add(v,u); } for(int i = 1 ; i <= n ; i ++) data[i] = read(); cnt = 0; for(int i = 1 ; i <= n ; i ++) { int x = data[i]; int p = Miller_Rabin(x); if(p == 1) {//如果是质数就直接入列 mp[x].push_back(i); if(M[x] != 1) M[x] = 1 , D[++cnt] = x; continue; } for(int j = 1 ; Prime[j] * Prime[j] <= x ; j ++) while(x % Prime[j] == 0) { x /= Prime[j] , mp[Prime[j]].push_back(i); if(M[Prime[j]] == 0) M[Prime[j]] = 1 , D[++cnt] = Prime[j]; } if(x != 1) { mp[x].push_back(i); if(M[x] != 1) M[x] = 1 , D[++cnt] = x; } } for(int i = 1 ; i <= cnt ; i ++) { int len = mp[D[i]].size(); for(int j = 0 ; j < len ; j ++) if(!vis[mp[D[i]][j]]) Ans = max (Ans , solve(mp[D[i]][j],D[i])) ; for(int j = 0 ; j < len ; j ++) vis[mp[D[i]][j]] = 0 , dis[mp[D[i]][j]] = 0; } cout << Ans; return 0; } /* 4 1 2 1 3 1 4 2 2 2 2//这是一组 Hack 数据 */