一、并查集:
(洛谷P1551):例17-1:亲戚:
cpp
#include <iostream>
using namespace std;
#define MAXN 5010
int n, m, p, x, y;
int fa[MAXN];
int find(int x) {
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);//路径压缩优化
}
void join(int c1,int c2) {
int f1 = find(c1), f2 = find(c2);
if (f1 != f2) fa[f1] = f2;
}
int main() {
cin >> n >> m >> p;
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 0; i < m; i++) {
cin >> x >> y;
join(x, y);
}
for (int i = 1; i <= p; i++) {
cin >> x >> y;
if (find(x) == find(y)) {
cout << "Yes" << endl;
}
else {
cout << "No" << endl;
}
}
return 0;
}
(洛谷P1536):例17-2:村村通:
cpp
#include <iostream>
using namespace std;
#define MAXN 1010
int fa[MAXN];
int n,m;
int a, b;
#include <cstring>
#include <algorithm>
int tmp[MAXN];
int find(int x) {
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void join(int c1, int c2) {
int f1 = find(c1), f2 = find(c2);
if (f1 != f2) fa[f1] = f2;
}
int main() {
while (true) {
cin >> n;
if (n == 0) break;
for (int i = 1; i <= n; i++) fa[i] = i;//并查集初始化
memset(tmp, 0, sizeof(tmp));
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> a >> b;
join(a, b);
}
for (int i = 1; i <= n; i++) {
tmp[i] = find(i);
}
sort(tmp + 1, tmp + 1 + n);
int set=unique(tmp+1,tmp+n+1)-(tmp+1);
cout << set - 1 << endl;
}
return 0;
}
二、Hash表:
计数排序升级版:
值域[0,1e7]正常处理,值域[0,1e9]则可以取一个模数mod,定义一个大小为mod的数组,然后把每个数对mod取模。如果两个数对mod取模得到相同的值,那么就认为这两个数是相同的。
cpp
#include <iostream>
using namespace std;
#define mod 233333
int n, x, ans, a[mod + 2];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x;
x %= mod;
if (!a[x]) a[x] = 1, ans++;
}
cout << ans << endl;
return 0;
}
这个处理方法优势和劣势都很明显。
优势:有效减少了空间的利用,只需要定义一个大小为mod的数组。
劣势:如果有两个不同的数恰好对mod取模之后得到了相同的结果,那这个算法的正确性就得不到保证了。
如何优化算法,能够扬长避短?
哈希表(Hash表):
cpp
#include <iostream>
using namespace std;
#define mod 233333
int n, x, ans;
#include <vector>
vector <int> linker[mod + 2];
inline void insert(int x) {
for (int i = 0; i < linker[x % mod].size(); i++) {
if(linker[x%mod][i]==x) return;
}
linker[x % mod].push_back(x);
ans++;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x;
insert(x);
}
cout << ans<<endl;
return 0;
}
问题升级到字符串:(洛谷P3370):例17-3:字符串哈希:
cpp
#include <iostream>
using namespace std;
#include <string>
#include <vector>
#define MAXN 1510
#define base 261
#define mod 23333
int n, ans;
char s[MAXN];
vector <string> linker[mod + 2];
inline void insert() {
int hash = 1;
for (int i = 0; s[i]; i++) {
hash = (hash * base + s[i]) % mod;
}
string t = s;
for (int i = 0; i < linker[hash].size(); i++) {
if (linker[hash][i] == t) return;
}
linker[hash].push_back(t);
ans++;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> s;
insert();
}
cout << ans << endl;
return 0;
}
不断进行迭代运算hash=(hash*k+s[i])%mod即可。
基数k可以任选,但是一般来说不少于128(ASCII字符集的数量)。当然,求Hash值的方法并不唯一,例如如果字符集局限于a到z的小写字母,也可以把每个数字映射为0到25,此时基数是26.
(洛谷P3405)例17-4:省市:
因为值和前两个字母有关系,所以对每个字符串只保留前两个字母,相当于有N个二元组<ai,bi>,问<ai,bi>=<aj,bj>的(i,j)对数。
cpp
#include <iostream>
using namespace std;
#define mod 233333
long long ans;
int n;
#include <string>
#include <vector>
vector < pair<int, int> > linker[mod + 2];
string a, b;
inline int gethash(string a, string b) {
return (a[0] - 'A') + (a[1] - 'A') * 26 + (b[0] - 'A') * 26 * 26 + (b[1] - 'A') * 26 * 26 * 26;
}
inline void insert(int x) {
for (int i = 0; i < linker[x % mod].size(); i++) {
if (linker[x % mod][i].first == x) {
linker[x % mod][i].second++;//把x所对应的出现次数++
break;
}
}
linker[x % mod].push_back(pair<int, int>(x, 1));
}
int find(int x) {
for (int i = 0; i < linker[x % mod].size(); i++) {
if (linker[x % mod][i].first == x) {
return linker[x % mod][i].second;
}
}
return 0;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a >> b;
a[2] = 0;
if (a[0] != b[0] || a[1] != b[1]) {
ans += find(gethash(b, a));
}
insert(gethash(a, b));
}
cout << ans << endl;
return 0;
}
三、集合应用实例:
(洛谷P5250)例17-5:木材仓库:
问题:维护一个集合,可以插入一个元素x,同时判断x是否已经存在;查询x的前驱后继,x的前驱定义为小于x的最大的数,x的后继定义为大于x的最小的数。
虽然可以使用比较高级的数据结构(如平衡树或Trie)来维护集合,但是比较难实现。不过,可以通过调用STL里面的set来很方便地解决这个问题。set的本质是红黑树(一种比较优秀的平衡二叉树)。
set集合需要用到的头文件是set,其方法如下:
set <int> ds;建立一个名字叫作ds的、元素类型为int的集合。
ds.insert(x);在集合中插入一个元素,如果这个元素已经存在,则什么都不干。
ds.erase(x);在集合中删除元素x,如果这个数不存在,则什么都不干。
ds.erase(it);删除集合中地址为it的元素。
ds.end();返回集合中最后一个元素的下一个元素的地址。不过这个很少直接使用,而是配合其他方法进行比较,以确认某个元素是否存在。
ds.find(x);查询x在集合中的地址,如果这个数不存在,则返回ds.end()。
ds.lower_bound(x);查询不小于(大于等于)x的最小的数在集合中的地址,如果这个数不存在,则返回ds.end()。
ds.upper_bound(x);查询大于x的最小的数在集合中的地址,如果这个数不存在,则返回ds.end()。
ds.empty();如果集合是空的,则返回1,否则返回0。
ds.size();返回集合中元素的个数。
cpp
#include <iostream>
using namespace std;
#include <set>
set <int> ds;
int n;
int a, b;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >>a >>b;
if (a == 1) {
if (ds.find(b) == ds.end()) ds.insert(b);
else cout << "Already Exist" << endl;
}
else if (ds.empty()) {
cout << "Empty" << endl;
}
else {
set <int>::iterator i = ds.upper_bound(b);//i
set <int>::iterator j = i;
if (j != ds.begin()) j--;//j
if (i != ds.end() && b - (*j) > (*i) - b) j = i;//*i要求i!=ds.end(),选择j的值
cout << (*j) << endl;
ds.erase(j);//去除j指向的元素
}
}
return 0;
}
lower_bound返回的迭代器,可以对其++找到后继元素的迭代器,也可以--找到前驱元素的迭代器。需要注意指向元素的迭代器,如果已经是begin(),则不能--,如果是end(),则不能++。
(洛谷P5266)例17-6:学籍管理:
map关联集合的本质也是一颗红黑树,可以看作一个下标可以是任意类型的数组。
其头文件是map,可以调用map实现如下几个基础功能:
map <A,B> ds;
建立一个名字叫作ds、下标类型为A,元素类型为B的映射表,例如map <string,int> 就是一个将string映射到int的映射表。
ds[A]=B;
把这个"数组"中下标为A的位置的值变成B,这里下标可以是任意类型,不一定先限定于大于0的整数,比如map<string,string>ds,就可以进行ds["kkksc03"]="mascot"的操作。
ds[A];
访问这个"数组"中下标为A的元素,比如可以进行cout<<ds["kkksc03"]<<endl;这样的操作。
注意:在使用ds[A]访问"数组"下标为A的元素时,如果这个下标对应的元素不存在,则会自动创建下标为A、值为默认值(例如,所有数值类型的默认值是0,string字符串是空字符串)的元素。
ds.end();
返回映射表中最后一个元素的下一个元素的地址。这个很少直接单独使用,而是配合其他方法进行比较,以确认某个元素是否存在。
ds.find(x);
查询x在映射表中的地址,如果这个数不存在,则返回ds.end().
ds.empty();
如果映射表是空的,则返回1,否则返回0。
ds.size();
返回映射表中的元素个数。
ds.erase(A);
删除这个"数组"中下标为A的元素。
cpp
#include <iostream>
using namespace std;
int n;
int opt;
#include <map>
map <string, int> ds;
#include <string>
string name;
int score;
int main(){
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> opt;
if (opt == 1) {
cin >> name >> score;
ds[name] = score;
cout << "OK" << endl;
}
else if (opt == 2) {
cin >> name;
if (ds.find(name) == ds.end()) cout << "Not found" << endl;
else cout << ds[name] << endl;
}
else if (opt == 3) {
cin >> name;
if (ds.find(name) == ds.end()) cout << "Not found" << endl;
else {
cout << "Deleted successfully" << endl;
ds.erase(name);
}
}
else {
cout << ds.size() << endl;
}
}
return 0;
}
(洛谷P1102)例17-7:A-B数对:
cpp
#include <iostream>
using namespace std;
#include <map>
#define maxn 200010
map <int, int> ds;
int n, c;
int a[maxn];
long long ans;
int main() {
cin >> n >> c;
for (int i = 1; i <= n; i++) {
cin >> a[i];
ds[a[i]]++;
}
for (int i = 1; i <= n; i++) {
ans += ds[a[i] - c];
}
cout << ans << endl;
return 0;
}
四、习题:
(洛谷P1918):习题17-1:保龄球:
cpp
#include <iostream>
#include <map>
using namespace std;
#define maxn 100010
int n;
int a[maxn];
map <int, int> ds;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
ds[a[i]] = i;
}
int q;
cin >> q;
int a;
for (int i = 1; i <= q; i++) {
cin >> a;
cout << ds[a] << endl;
}
return 0;
}
(洛谷P1525)习题17-2:关押罪犯:(世纪好题)
考察:并查集+排序
cpp
#include <iostream>
using namespace std;
#include <algorithm>
int n, m;
int a[20010];//负责人
int b[20010];//敌人
typedef struct data {
int x, y, z;
}Data;
Data t[100010];
inline bool cmp(Data a,Data b) {
return a.z > b.z;
}
inline int find(int x) {
if (x == a[x])return x;
return a[x] = find(a[x]);
}
inline void join(int x,int y) {
int a1 = find(x);
int b1 = find(y);
if (a1 != b1) a[a1] = b1;
}
inline bool check(int a,int b) {
int a1 = find(a);
int b1 = find(b);
if (a1 == b1) return true;
return false;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) a[i] = i;//并查集初始化
for (int i = 1; i <= m; i++) {
cin >> t[i].x >> t[i].y >> t[i].z;
}
sort(t + 1, t + 1 + m, cmp);
for (int i = 1; i <= m+1; i++) {//细节m+1,如果本年内监狱中未发生任何冲突事件,请输出 0
if (check(t[i].x, t[i].y)) {
cout << t[i].z << endl;
break;
}
else{
if (!b[t[i].x]) b[t[i].x] = t[i].y;
else join(b[t[i].x], t[i].y);
if (!b[t[i].y]) b[t[i].y] = t[i].x;
else join(b[t[i].y], t[i].x);
}
}
return 0;
}
(洛谷P1621)习题17-3:集合:
80分,剩下两个点超时了,等学完素数筛后可以降低复杂度,目前无能为力。
cpp
#include <iostream>
using namespace std;
int a, b, p;
int fa[100010];
int tmp[100010];
int c[100010];
bool check_prime(int x) {
if (c[x] == 1) return true;
if (x == 0 || x == 1) return false;
for (int i = 2; i * i <= x; i++) {
if (x % i == 0) return false;
}
c[x] = 1;
return true;
}
bool check(int x,int y) {
for (int i = y; i >= p; i--) {
if (x % i == 0 && y % i == 0 && check_prime(i) ) return true;
}
return false;
}
int find(int x) {
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void join(int x,int y) {
int f1 = find(x), f2 = find(y);
if (f1 != f2) fa[f1] = f2;
}
int main() {
cin >> a >> b >> p;
for (int i = a; i <= b; i++) fa[i] = i;
for (int i = b; i > a; i--) {
for (int j = i - 1; j >= a; j--) {
if (check(i,j)) join(i, j);
}
}
int num = 0;
for (int i = a; i <= b; i++) {
tmp[find(i)]++;
}
for (int i = 1; i <= 100002; i++) {
if (tmp[i] != 0) num++;
}
cout << num << endl;
return 0;
}
(洛谷P1892):习题17-4:团伙:
注意并查集的初始化,差点忘了。vector的使用很精妙。
cpp
#include <iostream>
using namespace std;
int n,m;
char opt;
int o1, o2;
int fa[1010];
#include <vector>
vector <int> b[1010];
int find(int x) {
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void join(int x, int y) {
int f1 = find(x), f2 = find(y);
if (f1 != f2) fa[f1] = f2;
}
int tmp[1010];
int ans;
int main() {
cin >> n>>m;
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
for (int i = 1; i <= m; i++) {
cin >> opt;
if (opt == 'F') {
cin >> o1 >> o2;
join(o1, o2);
}
else {
cin >> o1 >> o2;
b[o1].push_back(o2);
b[o2].push_back(o1);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < b[i].size(); j++) {
for (int k = 0; k < b[b[i][j]].size(); k++) {
join(b[b[i][j]][k], i);
}
}
}
for (int i = 1; i <= n; i++) {
tmp[find(i)]++;
}
for (int i = 1; i <= 1002; i++) {
if (tmp[i] != 0) ans++;
}
cout << ans << endl;
return 0;
}
(洛谷P1955)习题17-5:程序自动分析:
没有学到离散化,1e9很难受,受限了。
(洛谷P4305):习题17-6:不重复数字:
60分map做法:
cpp
#include <iostream>
using namespace std;
#include <cstdio>
#include <map>
int t, n, num;
int main() {
cin >> t;
for (int i = 1; i <= t; i++) {
map <int, int> ds;
cin >> n;
for (int j = 1; j <= n; j++) {
cin >> num;
if (ds[num] == 0) ds[num]++, printf("%d ",num);
}
printf("\n");
}
return 0;
}
60分小丑hash做法:
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define mod 233333
#include <vector>
int t, n, num;
vector <int> ds[mod + 2];
int gethash(int x) {
return (x % mod + mod) % mod;
}
int main() {
cin >> t;
for (int i = 1; i <= t; i++) {
memset(ds, 0, sizeof(ds));
cin >> n;
for (int j = 1; j <= n; j++) {
cin >> num;
int flag = 0;
for (int k = 0; k < ds[gethash(num)].size(); k++) {
if (ds[gethash(num)][k] == num) {
flag = 1;
break;
}
}
if (flag == 0) {
ds[gethash(num)].push_back(num);
printf("%d ", num);
}
}
printf("\n");
}
return 0;
}
存数组里输出玄学过了:
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define mod 233333
#include <vector>
int t, n, num;
int ans[50010];
vector <int> ds[mod + 2];
int gethash(int x) {
return (x % mod + mod) % mod;
}
int add;
int main() {
cin >> t;
for (int i = 1; i <= t; i++) {
memset(ds, 0, sizeof(ds));
memset(ans, 0, sizeof(ans));
add = 0;
cin >> n;
for (int j = 1; j <= n; j++) {
cin >> num;
int flag = 0;
for (int k = 0; k < ds[gethash(num)].size(); k++) {
if (ds[gethash(num)][k] == num) {
flag = 1;
break;
}
}
if (flag == 0) {
ds[gethash(num)].push_back(num);
ans[add++] = num;
}
}
for (int i = 0; i < add; i++) printf("%d ", ans[i]);
printf("\n");
}
return 0;
}
玄学求解 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)和这个一样,不知道为啥。。。
(洛谷P3879)例17-7:阅读理解:
0分答案全WA,不知道为啥错,样例过了。
cpp
#include <iostream>
using namespace std;
#include <string>
int n,l,m;
#define mod 233333
string a;
#include <vector>
vector < pair< string,vector<int> > > ds[mod + 2];
int gethash(string s){
int ans=0;
for (int i = 0; i < s.size(); i++) {
ans = (ans * 26 + s[i]-'a') % mod;
}
return ans;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> l;
for (int j = 1; j <= l; j++) {
cin >> a;
int flag = 0;
for (int k = 0; k < ds[gethash(a)].size(); k++) {
if (ds[gethash(a)][k].first == a) {
flag = 1;
int flag1 = 0;
for (int dd = 0; dd < (ds[gethash(a)][k].second).size(); dd++) {
if ((ds[gethash(a)][k].second)[k] == i) {
flag1 = 1;
break;
}
}
if (flag1 == 1) break;
(ds[gethash(a)][k].second).push_back(i);
break;
}
}
vector <int> q;
q.push_back(i);
if(flag==0)
ds[gethash(a)].push_back(pair< string,vector<int> >(a,q));
}
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> a;
int flag = 0;
for (int j = 0; j < ds[gethash(a)].size(); j++) {
if (ds[gethash(a)][j].first == a) {
vector <int> y = ds[gethash(a)][j].second;
for (int k = 0; k <y.size(); k++) {
if(k!= y.size()-1)
cout << y[k]<<' ';
else
cout << y[k]<<(i==m ? "" : "\n");
}
flag = 1;
break;
}
}
if (flag == 0) cout << "\n";
}
return 0;
}
(洛谷P17-9):习题17-9:家谱:
cpp
#include <iostream>
using namespace std;
#include <string>
string a,fa;
#include <map>
map <string, string> ds;
string find(string s) {
if (s == ds[s]) return s;
return ds[s] = find(ds[s]);
}
int main() {
char ch;
cin >> ch;
while ( ch!= '$') {
cin >> a;
if (ch== '#') {
fa = a;
if (ds[a] == "") ds[a] = a;
}
else if (ch == '+') {
ds[a] = fa;
}
else {
cout << a << ' ' << find(a) << endl;
}
cin >> ch;
}
return 0;
}
若用到getchar() ,超时0分:
cpp
#include <iostream>
using namespace std;
#include <cstdio>
char ch;
#include <string>
string a,fa;
#include <map>
map <string, string> ds;
string find(string s) {
if (s == ds[s]) return s;
return ds[s] = find(ds[s]);
}
int main() {
while ((ch = getchar()) != '$') {
cin >> a;
if (ch== '#') {
fa = a;
if (ds[a] == "") ds[a] = a;
}
else if (ch == '+') {
ds[a] = fa;
}
else {
cout << a << ' ' << find(a) << endl;
}
getchar();
}
return 0;
}