[UOJ#192]【UR #14】最强跳蚤

[UOJ#192]【UR #14】最强跳蚤

试题描述

在人类和跳蚤的战争初期,人们凭借着地理优势占据了上风——即使是最强壮的跳蚤,也无法一下越过那一堵坚固的城墙。

在经历了惨痛的牺牲后,跳蚤国王意识到再这样下去,跳蚤国必败无疑。然而为了震慑跳蚤国的老冤家——猴族,跳蚤国那世界上最跳的坦克只能留在跳蚤国本土,无法派上用场。

于是跳蚤国王决定利用跳蚤国最尖端的技术,创造出最强的跳蚤来挽回败局。

上帝造跳蚤

为了避免这样的低级失误,跳蚤国王决定使用机器来帮助他创造跳蚤。他把它拥有的 \(n\) 种属性放在了 \(n\) 个容器中,然后他使用了 \(n-1\) 条橡胶软管将这 \(n\) 个容器连接成了一个树形结构(即任意两个容器之间有且只有一条简单路径)。

跳蚤国王的机器会使用这样的方式来创造跳蚤:跳蚤国王需要选择两个不同的容器 \(u,v(u \ne v)\),那么机器就会使用 \(u\)\(v\) 的简单路径上的所有的橡胶软管将这条路上的所有属性汇聚到一起制造跳蚤。注意:这时只有 \(u\)\(v\) 的简单路径上的橡胶软管被用到了。

每一条橡胶软管都有一个耐久度 \(w_i\)跳蚤国王认为一个制造的方案是安全的,当且仅当所有被用到的橡胶软管的耐久度的乘积是完全平方数

现在,跳蚤国王想要知道有多少种不同的制造方案是安全的。但是因为跳蚤国王日理万机,所以他让你——一个刚刚被抓来的人类俘虏来帮他计算答案。

两个制造方案是不同的当且仅当 \(u\) 不同或者 \(v\) 不同。

输入

第一行两个正整数 \(n\),表示容器的数目。

以下 \(n-1\) 行,每行三个正整数 \(u,v,w\) 表示一条软管连接 \(u,v\),耐久度为 \(w\)

输出

输出一行一个整数表示制造方案数。

输入示例
5
1 2 2
1 3 6
1 4 2
4 5 3
输出示例
4
数据规模及约定

对于所有数据,\(1 \le n \le 100000,1 \le w \le 10^8\),保证形成一棵树。

题解

每种权值只有最多 \(8\) 个质因子,所以我们可以暴力维护每个节点到跟的路径乘积的哈希值(就是将有哪些质因子是奇数、哪些是偶数哈希起来),然后统计有多少对点满足它们的哈希值相同即可。

#include <bits/stdc++.h>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

#define maxn 100010
#define maxm 200010
#define maxlog 27
#define UL unsigned long long
#define LL long long

namespace Sieve {
    const int maxv = 10110;
    
    int prime[maxv], cp;
    bool vis[maxv];
    
    void main() {
        const int n = maxv - 1;
        rep(i, 2, n) {
            if(!vis[i]) prime[++cp] = i;
            for(int j = 1; j <= cp && i * prime[j] <= n; j++) {
                vis[i*prime[j]] = 1;
                if(i % prime[j] == 0) break;
            }
        }
        return ;
    }
}
using Sieve::prime;
using Sieve::cp;

int n, m, head[maxn], nxt[maxm], to[maxm], dist[maxm];

void AddEdge(int a, int b, int c) {
    to[++m] = b; dist[m] = c; nxt[m] = head[a]; head[a] = m;
    swap(a, b);
    to[++m] = b; dist[m] = c; nxt[m] = head[a]; head[a] = m;
    return ;
}

UL RND() {
    UL ans = 0, x = (1 << 8) - 1;
    rep(i, 1, 8) ans = ans << 8 | (rand() & x);
    return ans;
}

map <int, UL> id;
map <UL, int> hh;
LL ans;
void dfs(int u, int fa, int val, UL nowh) {
    if(val) {
        int m = sqrt(val);
        for(int i = 1; prime[i] <= m; i++)
            while(val % prime[i] == 0) {
                if(!id.count(prime[i])) id[prime[i]] = RND();
                nowh ^= id[prime[i]];
                val /= prime[i];
            }
        if(val > 1) {
            if(!id.count(val)) id[val] = RND();
            nowh ^= id[val];
        }
    }
    if(!hh.count(nowh)) hh[nowh] = 1;
    else ans += hh[nowh]++;
    for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa) dfs(to[e], u, dist[e], nowh);
    return ;
}

int main() {
    srand((unsigned)time(NULL));
    
    Sieve::main();
    
    n = read();
    rep(i, 1, n - 1) {
        int a = read(), b = read(), c = read();
        AddEdge(a, b, c);
    }
    
    dfs(1, 0, 0, 0);
    
    printf("%lld\n", ans << 1);
    
    return 0;
}

转载于:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/8979381.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值