ABC235 - E - MST + 1 —— 最小生成树,离线操作

题目来源


题意:

给定一个 n 个点,m 条边无向连通图,每条边有权值 c i c_i ci,各不相同。所以,其最小生成树是唯一的。
q q q 次询问,每次给出一条边: x i , y i , w i x_i, y_i, w_i xi,yi,wi,表示两端点为 x i x_i xi y i y_i yi,权值为 w i w_i wi
问,加入这条边之后,该图的最小生成树会不会发生变化?
或者说,加入的这条边是否会在新的最小生成树中?

2 ≤ N ≤ 2 × 1 0 5 2≤N≤2×10^5 2N2×105
N − 1 ≤ M ≤ 2 × 1 0 5 N−1≤M≤2×10^5 N1M2×105
1 ≤ Q ≤ 2 × 1 0 5 1≤Q≤2×10^5 1Q2×105

分析:

如果每次询问都重新跑一遍最小生成树的话,复杂度太高。所以需要考虑离线操作。

将本地的边和询问的边放到一起排序,跑最小生成树。

按照权值从小到大遍历所有边:

  • 如果当前边所连接的两个端点已经连通,那么这条边没有贡献,跳过;
  • 如果两个端点没有连通:
    1、如果这条边是本地的边,那么将两个端点所在连通块合并;
    2、否则就是询问的边,则说明这条边是有贡献的,会出现在最小生成树中,将其标记下来。但是注意,不将其真正的放在生成树中,也就是不将其两端点所在连通块合并。

这样,便可以跑一遍生成树就可以判断所有询问边是否能在最小生成树中。

细节问题:
思路明确了,接下来就说说踩的坑。。
要标记出来询问的边,一开始想的做法是开一个结构体node,存下来这三个元素,然后用 map 标记。但是发现map中不能放结构体。
然后又想到可以像字符串哈希一样,把这三个数字映射成一个,然后用map标记。但是,131 和 13331 都映射不为唯一的。。
最后,在队友的提醒下,用vector把这三个数字存起来,用map把vector标记。心想,这总该唯一了吧。
但是,还是不对。
原因在于一个大细节问题!我把询问的边用map标记为1,如果发现这条边能够在最小生成树中就标记为2,然后这样写的:

vector<int> v;v.pb(x);v.pb(y);v.pb(z);
if(mp[v] == 1) mp[v] = 2;
else pre[find(x)] = find(y);

如果是询问的边,就把其标记为2,否则就是本地边,合并连通块。有什么问题么?
确实有问题。
因为题目中没说询问的点是不是互不相同的,也就是说,mp[v] 之前赋值为 1 之后,下一个又是同一个v,那么此时 mp[v] 已经不是 1 了,而是 2,那么就会执行 else 指令,将连通块合并!!于是就出现错误了。

解决方法是,把询问边的判断方式,改成 if(mp[v]) ...

啊,大细节!

下面是 AC 不易的代码:
方法1:vector映射

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

/**/

const int N = 500010, mod = 1e9+7;
int T, n, m;
map<vector<int>, int> mp;

struct node{
	int x, y, z;
}a[N], b[N];

int pre[N], cnt;
int ans[N];

bool cmp(node a, node b){
	return a.z < b.z;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

void kruskal()
{
	sort(a+1, a+cnt+1, cmp);
	
	for(int i=1;i<=cnt;i++)
	{
		int x = a[i].x, y = a[i].y, z = a[i].z;
		if(find(x) == find(y)) continue;
		
		vector<int> v;v.pb(x);v.pb(y);v.pb(z);
		if(mp[v]) mp[v] = 2;
		else pre[find(x)] = find(y);
	}
}

signed main(){
	Ios;
	int q;
	cin>>n>>m>>q;
	
	for(int i=1;i<=n;i++) pre[i] = i;
	
	while(m--)
	{
		int x, y, z;
		cin >> x >> y >> z;
		a[++cnt] = {x, y, z};
	}
	
	for(int i=1;i<=q;i++)
	{
		int x, y, z;
		cin >> x >> y >> z;
		a[++cnt] = {x, y, z};
		b[i] = {x, y, z};
		vector<int> v;v.pb(x);v.pb(y);v.pb(z);
		mp[v] = 1;
	}
	
	kruskal();
	
	for(int i=1;i<=q;i++)
	{
		int x = b[i].x, y = b[i].y, z = b[i].z;
		vector<int> v;v.pb(x);v.pb(y);v.pb(z);
		if(mp[v] == 2) cout<<"Yes\n";
		else cout<<"No\n";
	}
	
	return 0;
}

方法2:
可以在存边的结构体中再开一个变量 id,记录当前边是不是询问边,是第几个询问边。然后如果有贡献的话将其对应的答案数组直接赋值。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
map<int,int> mp;

/**/

const int N = 500010, mod = 1e9+7;
int T, n, m;

struct node{
	int x, y, w, id;
}a[N];

int ans[N];
int pre[N], cnt;

bool cmp(node a, node b){
	return a.w < b.w;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

void kruskal()
{
	sort(a+1, a+cnt+1, cmp);
	
	for(int i=1;i<=cnt;i++)
	{
		int x = a[i].x, y = a[i].y;
		if(find(x) == find(y)) continue;
		
		if(a[i].id) ans[a[i].id] = 1;
		else pre[find(x)] = find(y);
	}
}

signed main(){
	Ios;
	int q;
	cin>>n>>m>>q;
	
	for(int i=1;i<=n;i++) pre[i] = i;
	
	for(int i=1;i<=m;i++){
		cnt ++;
		cin >> a[cnt].x >> a[cnt].y >> a[cnt].w;
	}
	
	for(int i=1;i<=q;i++){
		cnt ++;
		cin >> a[cnt].x >> a[cnt].y >> a[cnt].w;
		a[cnt].id = i;
	}
	
	kruskal();
	
	for(int i=1;i<=q;i++){
		if(ans[i]) cout<<"Yes\n";
		else cout<<"No\n";
	}
	
	return 0;
}

方法3:哈希
其实,多试几个哈希值确实能水过去,这里用的是 13371。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

/**/

const int N = 500010, mod = 1e9+7;
int T, n, m;
int e[N], h[N], ne[N], w[N], idx;
map<int, int> mp; 

struct node{
	int x, y, z;
}a[N], b[N];

int pre[N], cnt;

bool cmp(node a, node b){
	return a.z < b.z;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

int get(int x, int y, int z){
	unsigned long long tx = x, ty = y, tz = z;
	return tx * 13371*13371 + ty * 13371 + tz;
}

void kruskal()
{
	sort(a+1, a+cnt+1, cmp);
	
	for(int i=1;i<=cnt;i++)
	{
		int x = a[i].x, y = a[i].y, z = a[i].z;
		if(find(x) == find(y)) continue;
		
		if(mp[get(x, y, z)]) mp[get(x, y, z)] = 2;
		else pre[find(x)] = find(y);
	}
}

signed main(){
	Ios;
	int q;
	cin>>n>>m>>q;
	
	for(int i=1;i<=n;i++) pre[i] = i;
	
	while(m--)
	{
		int x, y, z;
		cin >> x >> y >> z;
		a[++cnt] = {x, y, z};
	}
	
	for(int i=1;i<=q;i++)
	{
		int x, y, z;
		cin >> x >> y >> z;
		a[++cnt] = {x, y, z};
		b[i] = {x, y, z};
		mp[get(x, y, z)] = 1;
	}
	
	kruskal();
	
	for(int i=1;i<=q;i++)
	{
		int x = b[i].x, y = b[i].y, z = b[i].z;
		if(mp[get(x, y, z)] == 2) cout<<"Yes\n";
		else cout<<"No\n";
	}
	
	return 0;
}

思路想出来了实现挂了。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值