原题链接
题目描述
QwQ又是被洛谷翻译按在地上摩擦的一天。。。直接引用
给定一个有n个点,m条边的无向连通图,每条边有边权。 定义一次操作为:选择一条图中的边,并将其权值+1。试求最小的操作次数,使得操作后的图的最小生成树是唯一的。
解题思路
法一
这种和最小生成树有关的题,无非就是上来先求一下最小生成树,然后再仔细钻研最小生成树的性质怎么套上来。我们得到了一个最小生成树,考虑加上一条非树边之后会如何。加上一条边后会组成一个环,根据最小生成树的性质(在这里就不详细证明了),我们让这条边最大就不会有其他的最小生成树。如果这条边已经是最大了就直接忽略,反之则一定会等于环上除去这条边的边中最长的一条的权值,直接给这条边的权值加一即可。所以我们只需要解决找这个环上的最大值了,然而这又刚好是树上路径求最长边,直接倍增就行了。最后,用这个路径中的最长边和新加的这条边比较计算答案即可。
平均时间复杂度
Θ
(
m
log
m
+
n
log
n
+
m
log
n
)
\Theta(m\log m+n\log n+m\log n)
Θ(mlogm+nlogn+mlogn)。
#include<bits/stdc++.h>
using namespace std;
const int NN=2e5+4;
struct node
{
int u,v,w;
bool use;
bool operator<(const node&it)const
{
return w<it.w;
}
}edge[NN];
int fa[NN],d[NN],up[24][NN],maxw[24][NN];
vector<pair<int,int> >g[NN];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void dfs(int u,int f,int w)
{
d[u]=d[f]+1;
up[0][u]=f;
maxw[0][u]=w;
for(int i=1;i<=20;i++)
{
up[i][u]=up[i-1][up[i-1][u]];
maxw[i][u]=max(maxw[i-1][u],maxw[i-1][up[i-1][u]]);
}
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i].first;
if(v==f)
continue;
dfs(v,u,g[u][i].second);
}
}
int maxedge(int x,int y)
{
if(d[x]<d[y])
swap(x,y);
int res=0;
for(int i=20;~i;i--)
if(d[up[i][x]]>=d[y])
{
res=max(res,maxw[i][x]);
x=up[i][x];
}
if(x==y)
return res;
for(int i=20;~i;i--)
if(up[i][x]!=up[i][y])
{
res=max(res,max(maxw[i][x],maxw[i][y]));
x=up[i][x];
y=up[i][y];
}
return max(res,max(maxw[0][x],maxw[0][y]));
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=m;i++)
scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
sort(edge+1,edge+1+m);
int cnt=0;
for(int i=1;i<=m;i++)
{
int fu=find(edge[i].u),fv=find(edge[i].v);
if(fu!=fv)
{
cnt++;
fa[fv]=fu;
edge[i].use=true;
g[edge[i].u].push_back(make_pair(edge[i].v,edge[i].w));
g[edge[i].v].push_back(make_pair(edge[i].u,edge[i].w));
if(cnt==n-1)
break;
}
}
dfs(1,0,0);
int ans=0;
for(int i=1;i<=m;i++)
if(!edge[i].use&&edge[i].w==maxedge(edge[i].u,edge[i].v))
ans++;
printf("%d",ans);
return 0;
}
法二
刚刚法一给出了一个思路:找一条能在树上组成环且这个环的最大值不止一个的边,我们现在直接在这个思路上做工作。之前我们发现,两个边只有在权相等的情况下才有可能对答案有贡献,所以我们可以把所有边权相等的边放在一起处理。可以先假设所有的边加上之后都对答案有贡献,然后除去没有贡献的边。在合并时,如果之前(还没有加入某个权的边之前)两个连通块就已经合并了,就一定不会对答案有贡献,因为加上这条边之后组成的环其他边都更小。然后考虑相同边权之间的冲突,如果两个连通块需要合并则加入这条边之前没有相同的边权加入,一定没有贡献,但是合并之后所有连通这两个连通块的相同边权的边都会对答案有贡献。于是,我们可以边做
K
r
u
s
k
a
l
Kruskal
Kruskal边算答案。
平均时间复杂度
Θ
(
m
log
m
)
\Theta(m\log m)
Θ(mlogm)。
#include<bits/stdc++.h>
using namespace std;
const int NN=2e5+4;
struct node
{
int u,v,w;
bool use;
bool operator<(const node&it)const
{
return w<it.w;
}
}edge[NN];
int fa[NN],maxx[NN];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=m;i++)
scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
sort(edge+1,edge+1+m);
int ans=0,i=1;
while(i<=m)
{
int j=i;
while(edge[i].w==edge[j].w)
j++;
for(int k=i;k<j;k++)
if(find(edge[k].u)!=find(edge[k].v))
ans++;
for(int k=i;k<j;k++)
{
int fu=find(edge[k].u),fv=find(edge[k].v);
if(fu!=fv)
{
ans--;
fa[fv]=fu;
}
}
i=j;
}
printf("%d",ans);
return 0;
}