牛客多校8 - All-Star Game(线段树分治+并查集按秩合并的撤销操作)

题目链接:点击查看

题目大意:有 n 个球员和 m 个球迷,一个球员可能是多个球迷的粉丝,需要选择最少的球员进行比赛,使得所有的球迷都愿意观看(对于每个球迷来说,都有至少一个其喜欢的球员入选比赛)

对于球迷与球员之间的关系,如果球迷 a 喜欢球员 b ,且球迷 c 喜欢球迷 b ,那么球迷 a 也会喜欢球迷 c 所喜欢的其他球员,对于球迷 c 同理

现在有 q 次粉丝关系的修改(增加或删除),对于每次修改后回答询问

题目分析:画画图不难看出,球迷与球员连边,设 cnt 为总的连通块的个数,设 cntn 为有多少个孤立的球员,cntm 有多少个孤立的球迷,举个例子:

上图的关系中,cnt = 3 , cntn = 1 , cntm = 0

对于每次询问,如果 cntm > 0 的话,意思就是有 cntm 个球迷没有喜欢的球员,答案显然为 -1 ,除此之外,答案都为 cnt - cntn

这样的话题目就转换为了维护一个并查集的关系就好了,但是并查集需要支持增加和删除操作,如果只需要维护一个 cnt 变量的话,直接用并查集的删除这个算法足够,但同时还需要维护 cntn 和 cntm ,用并查集的删除就不太好维护了

这里先说一下 cntn 和 cntm 该如何维护吧,因为每次连边一定是球迷连向球员的一条边,假设 x 为球迷,y 为球员:

  1. 添加 ( x , y ) 这条边:
    1. 如果 x 之前是孤立的球迷,则连通后 cntm 减一
    2. 如果 y 之前是孤立的球员,则连通后 cntn 减一
  2. 删除 ( x , y ) 这条边:
    1. 如果 x 之后是孤立的球迷,则连通后 cntm 加一
    2. 如果 y 之后是孤立的球员,则连通后 cntn 加一

既然不能用并查集的删除,那么可以换一个方向思考,删除一条边等于撤销这条边,所以我们可以使用并查集按秩合并中的撤销操作来表示删除(其实就是用栈维护一下合并的信息,撤销的时候再边出栈边撤销就好了)

鉴于这个题目允许离线操作,可以用线段树分治来实现,线段树的下标作为时间单位,这样每个叶子结点就代表了每个询问,递归的时候用并查集维护一下联通性就好了

时间复杂度应该是 qlogq * log( n + m ),因为并查集是按秩合并的,每次查询需要 log n 的时间复杂度

代码:

#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=4e5+100;

map<int,int>pre[N];

int f[N],rk[N],ans[N],cnt,cntn,cntm;

struct revo
{
	int cnt,cntn,cntm;
	int fax,fay;
	int rkx,rky;
};

struct Node
{
	int l,r;
	vector<pair<int,int>>node;//位于[l,r]这段时间内的边有哪些
	vector<revo>st;//并查集的撤销用
}tree[N<<2];

int find(int x)
{
	return f[x]==x?x:find(f[x]);
}

revo merge(int x,int y)//并查集的合并,返回合并之前的信息revo用于撤销
{
	revo ans;
	int xx=find(x),yy=find(y);
	ans.cnt=cnt,ans.cntm=cntm,ans.cntn=cntn;
	ans.fax=xx,ans.fay=yy;
	ans.rkx=rk[xx],ans.rky=rk[yy];
	if(xx!=yy)
	{
		if(rk[xx]==1)
			cntm--;
		if(rk[yy]==1)
			cntn--;
		cnt--;
		if(rk[xx]>rk[yy])
			swap(xx,yy);
		f[xx]=yy;
		rk[yy]=max(rk[yy],rk[xx]+1);
	}
	return ans;
}

void revocation(revo node)//并查集的撤销,将并查集恢复至node状态
{
	cnt=node.cnt;
	cntn=node.cntn;
	cntm=node.cntm;
	f[node.fax]=node.fax;
	f[node.fay]=node.fay;
	rk[node.fax]=node.rkx;
	rk[node.fay]=node.rky;
}

void build(int k,int l,int r)
{
	tree[k].l=l;
	tree[k].r=r;
	if(l==r)
		return;
	int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}

void update(int k,int l,int r,pair<int,int>val)
{
	if(l>r)
		return;
	if(tree[k].l>r||tree[k].r<l)
		return;
	if(tree[k].l>=l&&tree[k].r<=r)
	{
		tree[k].node.push_back(val);
		return;
	}
	update(k<<1,l,r,val);
	update(k<<1|1,l,r,val);
}

void dfs(int k)//线段树分治
{
	for(auto it:tree[k].node)//并查集的合并
		tree[k].st.push_back(merge(it.first,it.second));
	if(tree[k].l==tree[k].r)
	{
		if(cntm>0)
			ans[tree[k].l]=-1;
		else
			ans[tree[k].l]=cnt-cntn;
	}
	else
	{
		dfs(k<<1);
		dfs(k<<1|1);
	}
	while(tree[k].st.size())//并查集的撤销
	{
		revocation(tree[k].st.back());
		tree[k].st.pop_back();
	}
}

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,q;//n:球员 m:球迷 edge:m->n
	scanf("%d%d%d",&n,&m,&q);
	cnt=n+m,cntn=n,cntm=m;
	build(1,1,q);
	for(int i=1;i<=n;i++)
	{
		int k;
		scanf("%d",&k);
		while(k--)
		{
			int x;
			scanf("%d",&x);
			pre[x][i]=1;
		}
	}
	for(int i=1;i<=q;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		if(!pre[a].count(b))
			pre[a][b]=i;
		else
		{
			update(1,pre[a][b],i-1,make_pair(a+n,b));
			pre[a].erase(b);
		}
	}
	for(int i=1;i<=m;i++)
		for(auto it:pre[i])
			update(1,it.second,q,make_pair(i+n,it.first));
	dfs(1);
	for(int i=1;i<=q;i++)
		printf("%d\n",ans[i]);



















    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frozen_Guardian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值