题目链接:点击查看
题目大意:给出一张由 n 个点 m 条边组成的连通图,有 q 次询问,每次询问给出一个边集,需要判断这些边是否可以同时出现在最小生成树上
题目分析:需要用到的一个性质是,对于同一个询问来说,不同权值的选择是相互独立的,但是相同权值之间会相互影响,所以我们可以对每个询问的每条边单独拿出来讨论
在进行正常的最小生成树的操作时,对于每次操作的当前边 i ,需要处理所有询问中边权与当前边权相等的边,因为当前处理的这些询问的边权都相等,所以我们按照询问的 id 分组单独讨论,对于每个 id 来说,将这些边权相等的边都加入到并查集中后如果发现有环的话,那就说明第 id 个询问的答案是 NO ,如此处理即可
需要注意的是,上面的橙色的字,只是判断每个询问的可行性用的,并不属于最小生成树的过程,所以在进行上述插入过程后,需要及时撤销
因为涉及到了并查集的撤销,所以不能进行路径压缩,总的时间复杂度就是 mlogm 的
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=5e5+100;
struct Edge
{
int u,v,w,id;
bool operator<(const Edge& t)const
{
if(w!=t.w)
return w<t.w;
return id<t.id;
}
}edge[N],qu[N];
struct revo
{
int fax,fay;
int rkx,rky;
};
int f[N],rk[N],tot;
bool ans[N];
stack<revo>st;
int find(int x)
{
return f[x]==x?x:find(f[x]);
}
bool merge(int x,int y)
{
int xx=find(x),yy=find(y);
if(xx==yy)
return false;
revo temp;
temp.fax=xx,temp.fay=yy;
temp.rkx=rk[xx],temp.rky=rk[yy];
st.push(temp);
if(rk[xx]>rk[yy])
swap(xx,yy);
f[xx]=yy;
rk[yy]=max(rk[yy],rk[xx]+1);
return true;
}
void revocation(int k)
{
while(k--)
{
revo node=st.top();
st.pop();
f[node.fax]=node.fax;
f[node.fay]=node.fay;
rk[node.fax]=node.rkx;
rk[node.fay]=node.rky;
}
}
void init()
{
for(int i=1;i<N;i++)
{
f[i]=i;
rk[i]=1;
}
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
init();
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
int q;
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
ans[i]=true;
int k;
scanf("%d",&k);
while(k--)
{
int num;
scanf("%d",&num);
tot++;
qu[tot]=edge[num];
qu[tot].id=i;
}
}
sort(edge+1,edge+1+m);
sort(qu+1,qu+1+tot);
int pos=1;//记录qu数组的下标
for(int i=1;i<=m;i++)
{
int cnt=0;//共需要撤销多少条边
while(qu[pos].w==edge[i].w)
{
if(qu[pos].id!=qu[pos-1].id)//如果相邻两个询问的边不属于同一个组,则需要撤销
{
revocation(cnt);
cnt=0;
}
if(!merge(qu[pos].u,qu[pos].v))//如果询问的两个点早已经合并,说明当前的边不是必须的,更新答案
ans[qu[pos].id]=false;
else//否则在需要撤销的数量上加一
cnt++;
pos++;
}
revocation(cnt);
merge(edge[i].u,edge[i].v);
}
for(int i=1;i<=q;i++)
if(ans[i])
puts("YES");
else
puts("NO");
return 0;
}