hdu5923 Prediction(并查集/可继承的并查集+并查集合并)

题目

n(n<=500)个点,m(m<=1e4)条边,代表一个图G

以下m-1条边,生成一棵m个节点的魔法树

以下m条边,第i条边为树上的第i个节点所保存,

q(q<=5e4)个询问,

每次给出k个节点,释放这些节点本身保存的边,

也释放这些节点在魔法树上的祖先节点保存的边,

这些边和原图G的所有节点构成一个新图G’

对于每个询问,求G’的联通块的个数

数据保证,对于每个样例,所有k之和<=3e5

思路来源

https://blog.csdn.net/a664607530/article/details/74144263

https://blog.csdn.net/fnoi11awyfeng/article/details/81951160

题解

对于树上的每个节点,开一个并查集,

u所在的并查集维护的是,只考虑u这个节点,图G’的连通关系是怎样的

那么u实际上考虑的是其到根的这条链上所有节点,所以需要继承其父亲的连通关系

在dfs预处理时,处理出u的关系,并将u所保存的边释放,

 

对于每个查询,暴力地将每个点u所在的并查集关系合并成一个并查集,

实际的操作是,遍历原图G的每一个节点i,

如果i在树上节点u的这个图里的祖先是j,说明在u这个图里i和j是连通的,

那么在总的图里,i和j就一定是连通的,在总的并查集里合并i和j即可(如果i不等于j)

总的并查集,在代码里体现为0号节点维护的并查集

代码复杂度应该是O(T*\sum_{i=1}^{q}k_{i}*n),大概单组1.5e8叭,具体也不知如何

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm> 
#include <vector> 
using namespace std;
typedef pair<int,int> P;
const int maxn=505;	
const int maxm=1e4+5;
//原图有n个点m条边 但是新树有m个点 
int T,n,m,q;
int u,v,num;
int ans;
vector<int>E[maxm];//新树每个节点存的儿子 
P edge[maxm];//新树每个节点屯的边 
struct DSU
{
	int par[maxn];//原图的连通关系 
	void init(int n=500)
	{
		for(int i=1;i<=n;++i)
		par[i]=i;	
	}
	void copy(const DSU &b)
	{
		for(int i=1;i<=n;++i)
		par[i]=b.par[i];
	}
	int find(int x)
	{
		return par[x]==x?x:par[x]=find(par[x]);
	}
	void unite(int x,int y)
	{
		x=find(x),y=find(y);
		if(x==y)return;
		par[x]=y;
	}
}e[maxm];//新树里,每个节点建一个并查集 
void dfs(int u,int fa)
{
	e[u].copy(e[fa]);//可继承并查集 
	e[u].unite(edge[u].first,edge[u].second);//释放u藏的边将其合并 
	for(int i=0;i<E[u].size();++i)
	{
		int v=E[u][i];
		dfs(v,u);
	} 
}
int main()
{
	scanf("%d",&T);
    for(int cas=1;cas<=T;++cas)
    {
	 scanf("%d%d",&n,&m);
	 for(int i=1;i<=m;++i)//新树有m个点 
	 E[i].clear();
	 for(int i=1;i<m;++i)
	 {
	 	scanf("%d",&u);
		E[u].push_back(i+1); 
	 }
	 for(int i=1;i<=m;++i)
	 {
	 	scanf("%d%d",&u,&v);
	 	edge[i]=P(u,v);//每个点可以释放一条边 
	 } 
	 e[0].init(n);
	 dfs(1,0);
	 printf("Case #%d:\n",cas);
	 scanf("%d",&q);
	 while(q--)
	 {
	   ans=0;
	   e[0].init(n);
	   scanf("%d",&num);
	   for(int i=1;i<=num;++i)
	   {
	   	scanf("%d",&u);//在u所在的链并查集里 
	   	for(int j=1;j<=n;++j)
	   	{
	   		int fa=e[u].find(j);//在u所在并查集里,fa和j在同一个集合 
	   		if(fa!=j)e[0].unite(fa,j);//所以,在0这个并查集里,fa和j也该在同一个集合里 
	   	}
	   }
	   for(int i=1;i<=n;++i)
	   if(e[0].find(i)==i)ans++;
	   printf("%d\n",ans);
	 } 
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Code92007

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

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

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

打赏作者

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

抵扣说明:

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

余额充值