D. Graph and Queries(可撤销并查集+启发式合并+set维护)

题目

题意:

    给定一张无向图,n个点,m条边。每个点都有点权,点权互不相同,有两种操作。
    操作1:1 v,输出v点所在的连通块的点权最大值,并将那个点的权值置为0。
    操作2:2 i,将第i条边删去。
     1 ≤ n ≤ 2 e 5 , 1 ≤ m ≤ 3 e 5 , 1 ≤ q ≤ 5 e 5 。 1≤n≤2e5,1≤m≤3e5,1≤q≤5e5。 1n2e5,1m3e5,1q5e5

分析:

    这种带有删边的题目一般都会考虑从后往前加边,但是这道题每次输出答案的时候同时修改了点权,所以从后往前似乎无法操作。再考虑如果没有删边的话,就是维护一个并查集,每个并查集再维护一个set,按点权大小排序,每次访问时就拿出最大的输出然后修改即可。
    那么其实删边是可以通过可撤销并查集来完成的,我们先把那些没有删去的边加入,再从后往前加入那些被删去的边,这样每次有删边操作时,我们只要把最新的操作撤销即可。考虑合并两个集合,那就把set的大小小的那一个都放到大的那里去就好了,撤销时遍历一下删去即可。

#include <iostream>
#include <set>
#include <vector>
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
using namespace std;

const int maxn = 5e5+5;
struct node{
	int id,val;
	node(int a,int b)
	{
		id = a;
		val = b;
	}
	bool operator<(const node&n)const
	{
		if( val == n.val ) return id < n.id;
		return val > n.val;
	}
};
set<node> s[maxn];
int parent[maxn],v[maxn];
vector<pii> tmp;
int vis[maxn];
pii e[maxn];
struct query{
	int op,v;
}q[maxn];

int find(int p)
{
	if( p == parent[p] ) return p;
	return find(parent[p]);
}
void done(pii v)
{
	int fx = find(v.fi),fy = find(v.se);
	if( fx == fy )
	{
		tmp.push_back(mp(-1,-1));
		return;
	}
	if( s[fx].size() > s[fy].size() ) swap(fx,fy);
	tmp.push_back(mp(fx,fy));
	set<node>::iterator it;
	for (it = s[fx].begin(); it != s[fx].end(); it++)
	{
		s[fy].insert(*it);
	}
	parent[fx] = fy;
}
void undone()
{
	pii z = tmp.back();
	tmp.pop_back();
	if( z.fi == -1 ) return;
	set<node>::iterator it;
	for (it = s[z.fi].begin(); it != s[z.fi].end(); it++)
	{
		node t = *it;
		if( v[t.id] == 0 ) continue;
		s[z.se].erase(*it);
	}
	parent[z.fi] = z.fi;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int n,m,qx;
	cin >> n >> m >> qx;
	for (int i = 1; i <= n; i++)
	{
		cin >> v[i];
		parent[i] = i;
		s[i].insert(node(i,v[i]));
	}
	for (int i = 1; i <= m; i++) cin >> e[i].fi >> e[i].se;
	for (int i = 1; i <= qx; i++)
	{
		cin >> q[i].op >> q[i].v;
		if( q[i].op == 2 ) vis[q[i].v] = 1;
	}
	for (int i = 1; i <= m; i++)
		if( vis[i] == 0 ) done(e[i]);
	for (int i = qx; i >= 1; i--)
		if( q[i].op == 2 ) done(e[q[i].v]);
	for (int i = 1; i <= qx; i++)
	{
		if( q[i].op == 1 )
		{
			int fx = find(q[i].v);
			while( s[fx].size() > 0 && v[(*s[fx].begin()).id] == 0 ) s[fx].erase(s[fx].begin());
			if( s[fx].size() == 0 ) cout << 0 << '\n';
			else
			{
				node t = *s[fx].begin();
				cout << v[t.id] << '\n';
				v[t.id] = 0;
			}
		}else undone();
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值