给定一棵 $n$ 个点的树,边有边权。求简单路径上的边的乘积为完全平方数的点对 $(x,y)\ ,\ x\ne y$ 的数目。
题解
Hash
一个数是完全平方数,当且仅当每个质因子出现次数都是偶数。
因此给每一个质因子赋一个随机权值,一个数的权值等于它所有出现次数为奇数的质因子权值的异或。那么边权乘积的权值就是边权权值的异或。问题转化为求有多少条路径异或值为0。
显然, $x$ 到 $y$ 异或和为0,等价于 $x$ 到根和 $y$ 到根异或和为0。因此求出一个点到根节点的路径的权值异或和,问题转化为求有多少个相等的数。排序之后统计即可。
分解质因数可以先筛出 $\sqrt z$ 内的质数,只用质数试除,单次的时间复杂度为 $O(\frac{\sqrt z_i}{\ln z_i})$ 。
时间复杂度 $O(n\frac{\sqrt z_i}{\ln z_i})$ 。
注意:由于生日攻击原理,权值的范围需要远大于 $n^2$ ,需要long long级别。UOJ测评环境为Linux,randmax为2147483647。
#include <map> #include <cstdio> #include <algorithm> #define N 100010 using namespace std; typedef long long ll; int prime[10010] , tot , np[10010] , head[N] , to[N << 1] , next[N << 1] , cnt; ll val[N << 1] , sum[N]; map<int , ll> mp; void init() { int i , j; for(i = 2 ; i <= 10000 ; i ++ ) { if(!np[i]) prime[++tot] = i; for(j = 1 ; j <= tot && i * prime[j] <= 10000 ; j ++ ) { np[i * prime[j]] = 1; if(i % prime[j] == 0) break; } } } inline void add(int x , int y , ll z) { to[++cnt] = y , val[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void dfs(int x , int fa) { int i; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa) sum[to[i]] = sum[x] ^ val[i] , dfs(to[i] , x); } int main() { init(); srand(20011011); int n , i , j , x , y , z; ll t , v , ans = 0; scanf("%d" , &n); for(i = 1 ; i < n ; i ++ ) { scanf("%d%d%d" , &x , &y , &z) , v = 0; for(j = 1 ; j <= tot ; j ++ ) { if(z % prime[j] == 0) { if(mp.find(prime[j]) == mp.end()) mp[prime[j]] = (ll)rand() << 31 | rand(); t = mp[prime[j]]; while(z % prime[j] == 0) z /= prime[j] , v ^= t; } } if(z != 1) { if(mp.find(z) == mp.end()) mp[z] = (ll)rand() << 31 | rand(); v ^= mp[z]; } add(x , y , v) , add(y , x , v); } dfs(1 , 0); sort(sum + 1 , sum + n + 1); for(i = j = 1 ; i <= n ; i = j) { while(j <= n && sum[i] == sum[j]) j ++ ; ans += (ll)(j - i) * (j - i - 1); } printf("%lld" , ans); return 0; }