CodeForces - 892E Envy(可撤销并查集)

题目链接:点击查看

题目大意:给出一张由 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;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/107829812