CF891C

当时赛场上做到这题时还剩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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值