测试地址:Edges in MST
题目大意: 给定一个带权无向连通图,因为最小生成树不唯一,于是问每一条边是一定在最小生成树上,还是可能在最小生成树上,还是不可能在最小生成树上。
做法: 本题需要用到最小生成树+并查集。
这题挺神的。首先肯定需要构造出一棵最小生成树,那么树上的边的答案只有“一定”和“可能”两种,而不在树上的边的答案只有“可能”和“不可能”两种。
接下来先讨论非树边的答案。根据最小生成树的一些优美性质,这条边的边权一定大于等于树中它两个端点之间的路径上边权最大值,不然求出的树就不是最小生成树了。于是可知,当且仅当这条边的边权和树上对应路径边权最大值相等的时候,这条边才可能出现在最小生成树中。怎么判断呢?将树边和非树边分别按边权排序,用两个指针,当非树边的边权处理到
x
x
x时,树边的边权应该处理到
x
−
1
x-1
x−1,处理的过程是,维护边权小于
x
x
x的树边连成的连通块。这样一来,如果当前边的两个端点在这种状态下没有连通,就说明它们在树上的路径上有边权大于
x
−
1
x-1
x−1的边,而这个边权又必须
≤
x
\le x
≤x,所以存在边权等于
x
x
x的边。反之,因为树上两点间有且仅有一条简单路径,显然就不成立了。用并查集维护即可。
然后我们讨论树边的答案。我们发现,在上述处理过程中,每当一条非树边被判为“可能”,那么树中它两个端点之间的路径上和它相等的边就都是“可能”了。否则,因为没有可能被任意一条非树边替换,所以剩下的边就是“一定”在最小生成树中了。但直接暴力复杂度很大,树上差分又不能处理“标记一条路径上所有长度为
x
x
x的边”这样的奇技淫巧,这怎么做呢?
这时候这题最神的地方就出现了。因为这个
x
x
x这次被标记后,之后再标记
x
x
x,再标记这条路径中这些边就没有必要了;而因为
x
x
x是这条路径上边权的最大值,而且这个
x
x
x是随着算法的进行而不断增大的,所以之后标记比
x
x
x大的一些数跟这条路径也没什么关系了。所以,我们在标记的同时,可以直接将这条路径缩为一个点,因为后面的操作对这条路径上的边再没有什么影响了。这样一来,因为每条边只会被缩一次,配合并查集的使用,时间复杂度就是
O
(
n
)
O(n)
O(n)的了。
于是我们就解决了这一题,时间复杂度为
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,first[100010]={0},tot=0,pre[100010]={0},ans[100010]={0};
int dep[100010]={0},fa[100010],preid[100010],prew[100010],fak[100010];
struct kruskaledge
{
int id,a,b,w;
bool type;
}e[100010];
struct edge
{
int id,v,w,next;
}ed[200010];
bool cmp(kruskaledge a,kruskaledge b)
{
return a.w<b.w;
}
bool cmp2(kruskaledge a,kruskaledge b)
{
if (a.w==b.w) return a.type<b.type;
else return a.w<b.w;
}
void insert(int a,int b,int id,int w)
{
ed[++tot].v=b;
ed[tot].next=first[a];
ed[tot].id=id;
ed[tot].w=w;
first[a]=tot;
}
int find(int *fa,int x)
{
int r=x,i=x,j;
while(fa[r]!=r) r=fa[r];
while(i!=r) j=fa[i],fa[i]=r,i=j;
return r;
}
void merge(int *fa,int x,int y)
{
int fx=find(fa,x),fy=find(fa,y);
fa[fx]=fy;
}
void kruskal()
{
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=m;i++)
{
if (find(fa,e[i].a)!=find(fa,e[i].b))
{
e[i].type=1;
merge(fa,e[i].a,e[i].b);
insert(e[i].a,e[i].b,e[i].id,e[i].w);
insert(e[i].b,e[i].a,e[i].id,e[i].w);
}
else e[i].type=0;
}
}
void dfs(int v)
{
for(int i=first[v];i;i=ed[i].next)
if(ed[i].v!=pre[v])
{
pre[ed[i].v]=v;
dep[ed[i].v]=dep[v]+1;
preid[ed[i].v]=ed[i].id;
prew[ed[i].v]=ed[i].w;
dfs(ed[i].v);
}
}
void up(int &x,int w)
{
if (prew[x]==w) ans[preid[x]]=1;
int nxt=find(fak,pre[x]);
merge(fak,x,nxt);
x=nxt;
}
void solve()
{
sort(e+1,e+m+1,cmp2);
for(int i=1;i<=n;i++)
fa[i]=fak[i]=i;
for(int i=1;i<=m;i++)
{
if (e[i].type) merge(fa,e[i].a,e[i].b);
else
{
if (find(fa,e[i].a)!=find(fa,e[i].b))
{
ans[e[i].id]=1;
int x=e[i].a,y=e[i].b;
while(x!=y)
{
if (dep[x]==dep[y])
{
up(x,e[i].w);
up(y,e[i].w);
}
else
{
if (dep[x]<dep[y]) swap(x,y);
up(x,e[i].w);
}
}
}
else ans[e[i].id]=2;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
e[i].id=i;
scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].w);
}
kruskal();
dep[1]=1;
dfs(1);
solve();
for(int i=1;i<=m;i++)
{
if (ans[i]==0) printf("any\n");
if (ans[i]==1) printf("at least one\n");
if (ans[i]==2) printf("none\n");
}
return 0;
}