【并查集】P1197 [JSOI2008]星球大战-洛谷

题目链接

https://www.luogu.com.cn/problem/P1197

在这里插入图片描述
在这里插入图片描述

解题思路

1.这题最容易想到的方法就是用并查集搜索;在每次摧毁一个节点之后,把与该节点相连的边全部擦去,重新扫描图;
	但是这个方法太费时,肯定不行;
2.`逆向思维`
  我们不妨在一开始建图的时候就不考虑会被摧毁的点,直接建最后的图;
  然后把每一次的摧毁操作当成修复操作;再利用并查集判断联通即可;

代码展示

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=4e5+10;
/*
1.存储所有边
2.存储所有被摧毁的点
3.遍历所有点,找出存在的边
4.依次恢复点,每次增加这个点能构成的所有边 
*/
struct edge{
	int from,to;
};
//存储边 
vector<int>G[maxn];
//存储图 
edge Edge[maxn];
bool ds[maxn];
int K[maxn];
int father[maxn];
int ans[maxn];
int Find(int x){
	if(x==father[x])
		return x;
	father[x]=Find(father[x]);
	return father[x];
}
int main(){
	int n,m;
	cin>>n>>m;
	int f=-1;
	for(int i=0;i<n;i++)father[i]=i;
	for(int i=0;i<m;i++){
		int a,b;
		cin>>a>>b;
		G[a].push_back(b);
		G[b].push_back(a);
		f++;
		Edge[f].from=a,Edge[f].to=b;
		f++;
		Edge[f].from=b,Edge[f].to=a;	
	}
	//初始化
	int k;
	cin>>k;
	for(int i=0;i<k;i++){
		cin>>K[i];
		ds[K[i]]=true;//被摧毁 
	} 
	int total=n-k;//值得注意的是,一开始的节点个数应该按照n-k处理 
	for(int i=0;i<f;i++){
		if(ds[Edge[i].from]==false&&ds[Edge[i].to]==false){
			//两个点都没被炸毁
			int ff=Find(Edge[i].from);
			int ft=Find(Edge[i].to);
			if(ff!=ft){
				father[ff]=ft;
				total--;
			} 
			
		}
	}
	ans[k]=total;
	for(int i=k-1;i>=0;i--){
		ds[from]=false;
		total++;//修复一个点,把这个点先当成独立的连通块 
		int from=K[i];
		for(int j=0;j<G[from].size();j++){
			int to=G[from][j];
			if(ds[to]==false){
			//一条边的两个点都没被炸毁
			int ff=Find(from);
			int ft=Find(to);
			if(ff!=ft){
				father[ff]=ft;
				total--;
			} 
			
	    	}
		} 
		ans[i]=total;
	}
	for(int i=0;i<=k;i++)
		cout<<ans[i]<<endl;
	return 0;
}

总结

1.这题的难度并不大,只要我们能转过来想一想;
2.另外就是在创建标记符的时候,像flag,一定要记住true与false分别代表什么意思;

拓展练习

https://www.luogu.com.cn/problem/P1653
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

高冷小伙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值