有关并查集的基本操作和三个方面应用参考我之前写过的文章
朴素并查集:合并集合,询问是否在一个集合中
#include
using namespace std;
const int N = 5010;
int n,m,c;
int p[N];
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m >> c;
for(int i=1;i<=n;i++) p[i] = i; // 初始化并查集
while(m--)
{
int a,b;
cin >> a >> b;
p[find(a)] = find(b);
}
while(c--)
{
int a,b;
cin >> a>> b;
if(find(a) == find(b)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
朴素并查集的应用:一个集合用它的祖宗结点表示
#include
using namespace std;
const int N = 1010;
int n,m;
int p[N];
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
int main()
{
while(cin >> n >> m,n)
{
int res=0;
for(int i=1;i<=n;i++) p[i] = i; // 初始化并查集
while(m--)
{
int a,b;
cin >> a >> b;
p[find(a)] = find(b);
}
for(int i=1;i<=n;i++)
if(p[i]==i ) res++;
cout<<res-1<<endl;
}
return 0;
}
字符串哈希,是处理字符串的利器,既可以手写,也可以容器实现
另一模板题:字符串哈希
代码1(手写)
#include
#include
#include
using namespace std;
typedef unsigned long long ULL; // 溢出自动取模
const int N = 10010,P = 131; // P = 131(经验值),假设rp++
int n;
unordered_set<ULL> nums; // 自动去重
ULL hash_(string s) // 把字符串转换为数字
{
ULL res=0;
for(int i=0;i<s.size();i++)
{
res = res * P + s[i];
}
return res;
}
int main()
{
string s;
cin >> n;
for(int i=0;i<n;i++)
{
cin >> s;
ULL a = hash_(s);
nums.insert(a);
}
cout<<nums.size();
return 0;
}
代码2(利用vector排序+去重)
#include
#include
#include
using namespace std;
vector<string> ss;
int main()
{
int n;
cin >> n;
while(n--)
{
string s;
cin >> s;
ss.push_back(s);
}
// 排序加去重组合使用
sort(ss.begin(),ss.end());
ss.erase(unique(ss.begin(),ss.end()), ss.end());
cout<<ss.size();
return 0;
}
代码3(直接使用集合容器)
#include
#include
using namespace std;
int main()
{
unordered_set<string> ss; // 直接使用集合容器
int n;
cin >> n;
while(n--)
{
string s;
cin >> s;
ss.insert(s);
}
cout<<ss.size();
return 0;
}
嵌套map,C++11后有
unordered_map
(原理是哈希,操作是O(1)),map
操作是O(logn),操作函数一样
#include
#include
using namespace std;
const int N = 100010,base = 131;
map<int,map<int,int> > m; // >
int main()
{
int n;
cin >> n;
int res=0;
for(int i=0;i<n;i++)
{
string a,b;
cin >> a >> b;
int aid = a[0] * base + a[1],bid = b[0] * base + b[1]; // 字符串哈希
if(aid != bid) // 如果城市和州相同,不符合条件
{
res += m[aid][bid];
m[bid][aid] ++;
}
}
cout<<res<<endl;
return 0;
}
用
set
容器维护即可
#include
#include
#include
using namespace std;
const int N = 100000;
set<int> S;
int main()
{
int n;
cin >> n;
int op,len;
while(n--)
{
cin >> op >> len;
if(op==1)
{
if(S.count(len) ) cout<<"Already Exist"<<endl;
else S.insert(len);
}
else
{
if(S.size()==0) cout<<"Empty"<<endl;
else
{
set<int>::iterator i = lower_bound(S.begin(),S.end(),len);
auto j = i;
if(j!=S.begin()) j--;
if(i!=S.end() && len-(*j) > (*i)-len) j = i; // 比较
cout<<(*j)<<endl;
S.erase(j); // 删除
}
}
}
return 0;
}
用
unordered_map
容器维护即可
#include
#include
using namespace std;
unordered_map<string,int> S;
int main()
{
int n;
cin >> n;
int op,score;
string name;
while(n--)
{
cin >> op;
if(op==1)
{
cin >> name >> score;
S[name] = score;
cout<<"OK"<<endl;
}
else if(op==2)
{
cin >> name;
if(S.count(name) == 0) cout<<"Not found"<<endl;
else cout<<S[name]<<endl;
}
else if(op==3)
{
cin >> name;
if(S.count(name) == 0) cout<<"Not found"<<endl;
else
{
cout<<"Deleted successfully"<<endl;
S.erase(name);
}
}
else
{
cout<<S.size()<<endl;
}
}
return 0;
}
用
unordered_map
作映射,数字->次数。
另外一个重要思想:等式本身就带一个条件,通过枚举a
就可以确定b
(极大减少枚举次数)
#include
#include
using namespace std;
unordered_map<int,int> S;
long long res;
int main()
{
int n,c;
cin >> n >> c;
for(int i=0;i<n;i++)
{
int x;
cin >> x;
S[x] ++;
}
for(auto it =S.begin();it!=S.end();it++)
{
int a=it->first;
int b=a-c;
if(S.count(b) == 0) continue;
if(b==a) res += 1ll*S[a] * (S[b]-1); // 可能爆int
else res += 1ll*S[a] * S[b];
}
cout<<res<<endl;
return 0;
}
用
unordered_map
映射,瓶子数->位置
#include
#include
using namespace std;
unordered_map<int,int> S;
int main()
{
int n,m;
cin >> n;
for(int i=1;i<=n;i++)
{
int x;
cin >> x;
S[x] = i;
}
cin >> m;
while(m--)
{
int t;
cin >> t;
if(S.count(t) == 0) cout<<0<<endl;
else cout<<S[t]<<endl;
}
return 0;
}
#include
#include
#include
using namespace std;
typedef pair<int,pair<int,int> > PIII;
const int N = 20010 * 2;
int n,m;
int p[N];
vector<PIII> S;
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for(int i=1;i<=2*n;i++) p[i] = i;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin >> a >> b >> c;
S.push_back({c,{a,b}});
}
sort(S.begin(),S.end()); // 排序
reverse(S.begin(),S.end()); // 从大到小排序
for(int i=0;i<S.size();i++)
{
int a=S[i].second.first,b=S[i].second.second,c=S[i].first;
int pa=find(a),pb=find(b);
if(pa!=pb)
{
p[find(a)] = find(b+n);
p[find(b)] = find(a+n);
}
else
{
cout<<c;
return 0;
}
}
cout<<0;
return 0;
}
#include
using namespace std;
const int N = 100010;
bool prime[N];
int p[N];
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
int main()
{
int a,b,pp;
cin >> a >> b >> pp;
int res = b-a+1; // 将答案初始化为a~b之间的个数,每次合并-1即可
for(int i=a;i<=b;i++) p[i] = i; // 初始化并查集
for(int i=2;i<=b;i++) // 埃氏筛法
{
if(!prime[i])
{
if(i >= pp) // 如果当前质数大于等于p才合并
{
for(int j=i*2;j<=b;j+=i)
{
prime[j] = true;
if(j-i >= a && find(j) != find(j-i)) // 把质数的倍数 合并到 第一个大于a的倍数上去
{
p[find(j)] = find(j-i);
res --;
}
}
}
else
{
for(int j=i*2;j<=b;j+=i) prime[j] = true;
}
}
}
cout<<res;
return 0;
}
并查集扩展域
祖宗节点表示一个集合
#include
using namespace std;
const int N = 1010*2;
int n,m;
int p[N];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for(int i=1;i<=2*n;i++) p[i] = i;
char op;
int x,y;
while(m--)
{
cin >> op >> x >> y;
if(op=='E')
{
p[find(y+n)] = find(x); // 只分析1~n的点,不能反
p[find(x+n)] = find(y);
}
else
{
p[find(x)] = find(y);
//p[find(x+n)] = find(y+n);
}
}
int res=0;
for(int i=1;i<=n;i++)
if(p[i] == i) res++;
cout<<res<<endl;
return 0;
}
先合并等价条件,再从不满足的条件中推出矛盾
由于数据范围太大( 10^9) 但观察发现只有10^5次询问,也即最多只会出现 2 × 10^5 个数(左右端点),所以离散化
#include
#include
using namespace std;
const int N = 2000010;
struct Query{
int x,y,e;
}query[N];
int n,m; // m 用来离散化分配下标
int p[N];
unordered_map<int,int> S;
int get(int x)
{
if(S.count(x) == 0) S[x] = ++ m;
return S[x];
}
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
int T;
cin >> T;
while(T--)
{
cin >> n;
S.clear();
for(int i=1;i<=n;i++)
{
int x,y,e;
cin >> x >> y >> e;
x=get(x),y=get(y);
query[i] = {x,y,e};
}
for(int i=1;i<=m;i++) p[i] = i;
for(int i=1;i<=n;i++)
{
if(query[i].e == 1)
{
int px=find(query[i].x),py=find(query[i].y);
p[px]=py;
}
}
bool has_conflict = false;
for(int i=1;i<=n;i++)
if(query[i].e == 0)
if(find(query[i].x) == find(query[i].y))
{
has_conflict = true;
break;
}
if(has_conflict) puts("NO");
else puts("YES");
}
return 0;
}
用
unordered_map
维护是否出现过,输入输出超过10^ 5 建议用scanf,printf
#include
#include
#include
using namespace std;
unordered_map<int,bool> S;
int main()
{
int t;
//cin >> t;
scanf("%d",&t); // 输入输出超过100000,用scanf,printf
while(t--)
{
S.clear();
int n;
//cin >> n;
scanf("%d",&n);
while(n--)
{
int x;
//cin >> x;
scanf("%d",&x);
if(!S[x])
{
//cout<
printf("%d ",x);
S[x] = true; //s出现过
}
}
//cout<
printf("\n");
}
}
用
unordered_map
维护 <单词,{行号集合}>
#include
#include
#include
#include
#include
using namespace std;
unordered_map<string,set<int> > S; // set 自动排序+去重,O(nlogn)
int n,l,m;
string s;
int main()
{
cin >> n;
for(int i=1;i<=n;i++)
{
cin >> l;
for(int j=0;j<l;j++)
{
cin >> s;
S[s].insert(i); // 还要去重
}
}
cin >> m;
while(m--)
{
cin >> s;
if(S.count(s))
{
for(auto c : S[s]) cout<<c<<" ";
}
cout<<endl;
}
return 0;
}
朴素并查集,映射为string->string,用
unordered_map
表示即可
#include
#include
using namespace std;
const int N = 50010;
unordered_map<string,string> S;
string find(string name)
{
if(S[name] != name) S[name] = find(S[name]);
return S[name];
}
int main()
{
char op;
string name;
string father;
while(cin >> op >> name,op!='$')
{
if(op == '#' || op=='+')
{
if(S.count(name) == 0) S[name] = name; // 并查集初始化
}
if(op=='#')
{
father = name;
}
else if(op=='+')
{
S[name] = father;
}
else
{
string t = find(name);
cout<<name<<" "<<t<<endl;
}
}
return 0;
}