解题思路:
首先我们考虑边权权值,题目说了边的权值就是边连接的俩个点权值乘积末尾0的数量,末尾0的数量是只和质因子2和质因子5有关的,因为10=2*5,所以说一个质因子2和一个质因子5就可以得到一个末尾0,所以我们对于边的权值只需要知道边连接的俩个点的权值所包含的因子2和因子5的数目就行了,这个地方我们需要注意一点ai的值最大可以达到1e14,对于一条边如果先将这条边连接的俩个点的权值相乘再来求因子2和因子5的个数是会爆long long的,我们可以分别求因子2和因子5的个数在加起来就可以避免爆long long 了。
首先我们正向考虑删除哪些边可以保证不影响图的连通性并且删去的边的权值之和最大,正向考考虑有点麻烦,正难则反这个是很常用的,我们来反向考虑一下,要使得删去的边的权值和最大,就是要使得留下的边的权值和最小并且使得图是连通的,这句话是不是很熟悉呢,这不就是最小生成树吗,所以只需要先求一遍最小生成树,然后所有边的权值总和减去最小生成树边权和就是答案。
首先来解释一下代码推出:
w=min(t[a].first+t[b].first,t[a].second+t[b].second); // 计算权值
解析:
建立边权 w = min(cnt5 + cnt5 ,cnt2 + cnt2),可以代表两个节点“权值乘积末尾0数量”的价值。
设第一个节点的权值为x,第二个节点的权值为y,两个节点的乘积为xy。我们分析xy后末尾0的个数的计算方法:
- 末尾0的个数实际上是x*y中10的个数,而10可以分解为2和5相乘。
- x中有cnt2个因子2,y中有cnt5个因子5,那么x*y中10的个数应该是min(cnt2, cnt5)。
因此,建立边权w=min(cnt5+cnt5,cnt2+cnt2)可以代表两个节点“权值乘积末尾0数量”的价值,符合数学逻辑。这样设计可以确保通过最小化(cnt2, cnt5)来保证权值的计算是正确的。
假设一个数有cnt2个2作为因子,cnt5个5作为因子,那末尾0的数量是min(cnt2, cnt5)。当我们考虑两个数相乘时,这两个数的2的因子和5的因子数量是累加的。也就是说,如果一个数x的2的因子数量为cnt2(x),5的因子数量为cnt5(x),另一个数y的相应因子数量为cnt2(y)和cnt5(y),那么x和y的乘积有cnt2(x)+cnt2(y)个2作为因子,cnt5(x)+cnt5(y)个5作为因子。因此,这个乘积末尾0的数量为min(cnt2(x)+cnt2(y), cnt5(x)+cnt5(y))。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
typedef pair<int,int> PII;
const int N=2e6+10;
int p[N]; // 并查集数组,用于存储每个节点的父节点
int find(int x) // 查找x的根节点
{
if (x!=p[x]) p[x]=find(p[x]); // 如果x不是根节点,递归查找其父节点,并进行路径压缩
return p[x]; // 返回x的根节点
}
struct node // 定义一个结构体,用于存储边的信息
{
int a,b,w; // a和b表示边的两个端点,w表示权值
}str[N];
bool cmp(node x,node y) // 比较函数,用于排序
{
return x.w<y.w; // 按照权值从小到大排序
}
PII t[N]; // 存储每个节点的权值末尾0的数量
int n,m; // n表示节点数,m表示边数
signed main()
{
ios; // 优化输入输出流
cin>>n>>m; // 输入节点数和边数
for (int i=1;i<=n;i++) p[i]=i; // 初始化并查集数组,每个节点的父节点都是自己
for (int i=1;i<=n;i++)
{
int x;
cin>>x; // 输入每个节点的权值
int a=0,b=0;
while (x%2==0&&x>0) // 统计权值末尾0的数量
{
x /=2;
a++;
}
while (x%5==0&&x>0)
{
x /=5;
b++;
}
t[i]={a,b};
}
for (int i=0;i<m;i++)
{
int a,b,w;
cin>>a>>b; // 输入每条边的两个端点
w=min(t[a].first+t[b].first,t[a].second+t[b].second); // 计算权值
str[i]={a,b,w}; // 存储边的信息
}
sort(str,str+m,cmp); // 对边按权值从小到大排序
int ans=0; // 初始化最大价值为0
for (int i=0;i<m;i++)
{
int a=str[i].a,b=str[i].b,w=str[i].w; // 获取当前边的端点和权值
int x=find(a),y=find(b); // 查找两个端点的根节点
if (x!=y) p[y]=x; // 如果两个端点的根节点不同,合并它们所在的集合
else ans +=w; // 如果两个端点的根节点相同,累加权值到最大价值中
}
cout<<ans; // 输出最大价值
return 0;
}