当时赛场上做到这题时还剩1小时,然后因为深夜精神不好加上对kruskal理解不够深刻,没有做出来,还直接导致房间里的hack都被别人抢走了。
题意是有一张连通图,每次询问是否有一颗最小生成树包含给定的一组边集。
首先,如果边集大小是1,那么求出最小生成树对应链上的最大值即可。
但如果边集大于1,就不行了。因为如果强行加上两条权值相等的边,可能会导致环的出现。
考虑kruskal,把边权从小到大排序进行处理。在kruskal的过程中,原图会有很多联通块,我们不必要关心这些联通块的生成树长什么样,只需要关心每个点在哪个联通块中。
注意到一条边可能在MST上,当且仅当这条边加进去的时候,其两端点不在同一个联通块中。
然而在判定一条边
(u1,v1,w1)
是否在同一个联通块时,可能会受到另一条边
(u2,v2,w2)
的影响(其中
w1
等于
w2
),所以我们在处理同属于一组询问且边权均相等的一组边集时,要排除掉不属于这组询问,但边权一样的其他边,这可以用支持撤销的并查集来实现。在把同一种边权全部做后,要把撤销掉的该种边权的边,全部加回去。
这样,就做完了此题。
关于如何实现支持撤销的并查集,我以前一直是按秩合并,实际上在这题中,可以把一次涉及到的父亲数组,给复制一遍,然后路径压缩。
感觉赛场上没做出这题真是亏,但自己菜,又有什么办法呢?
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=500005;
int u[N],v[N],w[N],n,m,i,f[N],g[N],T,qq,a[N],k,x,y,j;
vector<pair<int,int> >q[N],e[N];
bool wa[N];
inline int gfa(int x){return f[x]==x?x:f[x]=gfa(f[x]);}
inline int gfa2(int x){
return (a[x]<T?a[x]=T,g[x]=f[x]:0),(g[x]==x?x:g[x]=gfa2(g[x]));
}
int main(){
scanf("%d%d",&n,&m);
for(i=1;i<=m;++i)scanf("%d%d%d",u+i,v+i,w+i),e[w[i]].push_back(make_pair(u[i],v[i]));
for(i=1;i<=n;++i)f[i]=i;
for(scanf("%d",&qq),i=1;i<=qq;++i){
scanf("%d",&k);
while(k--)scanf("%d",&x),q[w[x]].push_back(make_pair(i,x));
}
for(i=1;i<500001;++i){
sort(q[i].begin(),q[i].end());
for(j=0;j<int(q[i].size());++j){
T+=!j || q[i][j-1].first<q[i][j].first;
x=gfa2(u[q[i][j].second]),y=gfa2(v[q[i][j].second]);
if(x==y)wa[q[i][j].first]=1;
else g[x]=y;
}
for(j=0;j<int(e[i].size());++j)f[gfa(e[i][j].first)]=gfa(e[i][j].second);
}
for(i=1;i<=qq;++i)puts(wa[i]?"NO":"YES");
return 0;
}