算法提升之-启发式并查集
今天给大家带来的是关于启发式的算法思想,这类型的题目对于大家的思维提升是有很大帮助的,首先需要先了解相关部分内容。
1.启发式合并的基本概念
2.启发式合并的相关部分
接下来我将通过几道例题来帮助大家更好地理解启发式合并的内容
题目一 修改数组
题目描述
给定一个长度为 N的数组A=[A1,A2,⋅⋅⋅,AN],数组中有可能有重复出现的整数。
现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改A2,A3,⋅⋅⋅,AN。
当修改 Ai时,小明会检查 Ai 是否在 A1 ∼ Ai−1中出现过。如果出现过,则小明会给 Ai 加上 1 ;如果新的 Ai仍在之前出现过,小明会持续给 Ai加 1 ,直 到 Ai没有在 A1Ai−1 中出现过。
当 AN 也经过上述修改之后,显然 A 数组中就没有重复的整数了。
现在给定初始的 A 数组,请你计算出最终的 A数组。
输入描述
第一行包含一个整数 N。
第二行包含 N个整数 A1,A2,⋅⋅⋅,AN。
其中,1≤N≤105,1≤Ai≤106。
输出描述
输出 N 个整数,依次是最终的A1,A2,⋅⋅⋅,AN。
输入案例:
5
2 1 1 3 4
输出案例:
2 1 3 4 5
代码部分:
///修改数组-并查集
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e6 + 10;
int a[N], pre[N];
// root(i)为大于等于i,且在a[1~i-1]中没有出现过的数字int root(int x)
{return pre[x] = (pre[x] == x ? x : root(pre[x]));
}int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int n;cin >> n;for (int i = 1; i <= n; ++i)cin >> a[i];// 初始化for (int i = 1; i <= 1e6; ++i)pre[i] = i;for (int i = 1; i <= n; ++i){cout << root(a[i]) << " ";a[i] = root(a[i]); // 要把a[i]变成转化后的a[i]再改根pre[root(a[i])] = pre[root(a[i] + 1)]; // 以为在这里a[i]已经出现了,所以root(a[i])要指向root(a[i]+1)}
}
这道题十分的巧妙,而处理这道题的关键就是要正确理解最后转移的部分pre[root(a[i])] = pre[root(a[i] + 1)];
问题描述
多米诺骨牌是一种玩具和游戏,由一组长方形的小方块组成,每个方块可分为两个部分,并在每个部分上标有从零到六或更多个点数。多米诺骨牌的长度通常是宽度的两倍。在游戏中,玩家将多米诺骨牌竖立并排成一行或一列,使相邻的骨牌的点数匹配。一旦某个骨牌被推倒,它会触发连锁反应,将相邻的骨牌推倒。
蓝蓝是一个有严重强迫症的大学生,是个完美主义者。他喜欢对称的形状,比如一个完美的圆形。他的手上有一副私人定制版多米诺骨牌,上面刻的不完全是数字,还有各种字符。这天,他突发奇想,如果严格按照多米诺骨牌的排放规则(相邻两骨牌只有一半相同的图案),能不能把它们放置成一个完整的首尾相接的圆形?
输入格式
第 1 行:一个整数 N,代表骨牌的总数。
第 2到 N+1 行:每行有两个长度不超过 3 的字符串,代表骨牌上的刻的信息,字符串仅包含小写字母和标准数字键对应的可见字符。
输出格式
如果能产生一个符合题目要求的序列,则输出 YES,不能则输出 NO,对于无法预测的结果则输出 UNKOWN。
样例输入1
3
a b
b c
c a
样例输出1
Yes
样例输入2
3
aa b
b cc
cc z
样例输出2
No
代码部分:
#include <bits/stdc++.h>
using namespace std;
map<string,string>pre;
map<string,int>cnt;
string root(string a){return pre[a]=pre[a]==a?a:root(pre[a]);
}
void merge(string a,string b){if(root(a)==root(b))return;pre[root(a)]=root(b);
}
int main()
{int n;cin>>n;string s3;while(n--){string s1,s2;cin>>s1>>s2;s3=s2;if(pre.count(s1)==0)pre[s1]=s1;if(pre.count(s2)==0)pre[s2]=s2;merge(s1,s2);cnt[s1]++;cnt[s2]++;}
for(auto &[x,y]:cnt){if(y&1){cout<<"NO";return 0;}
}
string jug=root(s3);
for(auto &[x,y]:pre){
if(root(x)!=jug){cout<<"NO";return 0;
}
}
cout<<"YES";return 0;
}
这道题是非常经典的一道启发式的并查集问题,大家可以好好做做。