对一些学过的算法模板进行总结
并查集
0x01:概念
并查集是一种可以动态维护若干个不重叠的结合,并且支持合并与查询的数据结构。
也就是擅长维护各种各样的具有传递的关系。
0x02:基本操作
- find(int x)操作 找x的祖宗结点
- merge(int x, int y)操作 把两个集合合并并为一个集合
0x03:模板
// 初始化,每个点都是一个独立的集合,且祖宗是本身
for(int i = 1; i <= n; ++i) p[i] = i;
//路径压缩:每一次运行find操作,都会把访问过的结点都指向祖宗。这种方法可以避免出题人刻意卡掉链式结构。复杂度O(logn)
int find(int x){
return x == p[x] ? x : p[x] = find(p[x];
}
void merge(int x, int y){
p[find(x)] = find(y);
}
题目总结
0x01:格子游戏
【题目】
Alice和Bob玩了一个古老的游戏:首先画一个 n×n 的点阵(下图 n=3 )。
接着,他们两个轮流在相邻的点之间画上红边和蓝边:
直到围成一个封闭的圈(面积不必为 1)为止,“封圈”的那个人就是赢家。
因为棋盘实在是太大了,他们的游戏实在是太长了!
他们甚至在游戏中都不知道谁赢得了游戏。
于是请你写一个程序,帮助他们计算他们是否结束了游戏?
【输入格式】
输入数据第一行为两个整数 n 和 m。
n表示点阵的大小,m 表示一共画了 m 条线。
以后 m 行,每行首先有两个数字 (x,y),代表了画线的起点坐标,接着用空格隔开一个字符,假如字符是 D,则是向下连一条边,如果是 RR 就是向右连一条边。
输入数据不会有重复的边且保证正确。
【输出格式】
输出一行:在第几步的时候结束。
假如 m 步之后也没有结束,则输出一行“draw”。
【解题思路】
题意的围成环这个条件,可以读出元素之间的连接性,可以使用并查集解题。
将每个点存入并查集内,
还有二维坐标转一维的用法。
【代码】
#include<iostream>
using namespace std;
const int N = 40010;
int p[N];
int n, m;
int get(int a, int b){ // 将二维降成一维
return a * n + b;
}
int find(int x){
if(x != p[x]){
p[x] = find(p[x]);
}
return p[x];
}
int main(){
cin >> n >> m;
for(int i = 0; i < n * n; ++i) p[i] = i; // 初始化
int res = 1;
while(m --){
int a, b;
char op[2];
scanf("%d%d%s", &a, &b, op);
a--;
b--;
int x = get(a, b);
int y;
if(*op == 'D') y = get(a + 1, b);
else y = get(a, b + 1);
if(find(x) == find(y)){
cout << res << endl;
break;
}
p[find(x)] = find(y);
res++;
}
if( m == -1){ // 注意用while退出的时-1
cout << "draw" << endl;
}
return 0;
}
0x02:程序自动机
【题目】
在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设x1,x2,x3,…代表程序中出现的变量,给定n个形如xi=xj或xi≠xj的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。
例如,一个问题中的约束条件为:x1=x2,x2=x3,x3=x4,x1≠x4,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。
【输入格式】
输入文件的第1行包含1个正整数t,表示需要判定的问题个数,注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第1行包含1个正整数n,表示该问题中需要被满足的约束条件个数。
接下来n行,每行包括3个整数i,j,e,描述1个相等/不等的约束条件,相邻整数之间用单个空格隔开。若e=1,则该约束条件为xi=xj;若e=0,则该约束条件为xi≠xj。
【输出格式】
输出文件包括t行。
输出文件的第k行输出一个字符串“YES”或者“NO”(不包含引号,字母全部大写),“YES”表示输入中的第k个问题判定为可以被满足,“NO”表示不可被满足。
【数据范围】
1≤n≤1000000
1≤i,j≤1000000000
【输入样例】
2
2
1 2 1
1 2 0
2
1 2 1
2 1 1
【输出样例】
NO
YES
【解题思路】
将等号的数存入并查集内.
使用离散化处理输入的数据,因为数值的范围过大,使用那么大的数组会浪费很多空间。
要先将所有情况保存,再先对等号的情况做出处理,再接着对不等号的情况做出判断得出结果。
【代码】
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;
const int N = 1000010;
typedef pair<int, int> PII;
int p[N * 2];
int n, cnt;
vector<PII> eqs, ueqs;
unordered_map<int, int> H;
int mapping(int x){
if(H.count(x)) return H[x];
return H[x] = cnt++;
}
int find(int x){
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
int main(){
int T;
cin >> T;
while(T -- ){
cin >> n;
cnt = 0;
eqs.clear();
ueqs.clear();
H.clear();
for(int i = 0; i < n; ++i){
int a, b, c;
cin >> a >> b >> c;
a = mapping(a);
b = mapping(b);
if(c) eqs.push_back({a, b});
else ueqs.push_back({a, b});
}
for(int i = 0; i < cnt; ++i) p[i] = i;
for(auto x : eqs){
p[find(x.first)] = find(x.second);
}
bool res = true;
for(auto x : ueqs){
if(find(x.first) == find(x.second)){
res = false;
break;
}
}
printf("%s\n", res ? "YES" : "NO");
}
return 0;
}
0x03:连通块中点的数量
【题目】
给定一个包含n个点(编号为1~n)的无向图,初始时图中没有边。
现在要进行m个操作,操作共有三种:
“C a b”,在点a和点b之间连一条边,a和b可能相等;
“Q1 a b”,询问点a和点b是否在同一个连通块中,a和b可能相等;
“Q2 a”,询问点a所在连通块中点的数量;
【输入格式】
第一行输入整数n和m。
接下来m行,每行包含一个操作指令,指令为“C a b”,“Q1 a b”或“Q2 a”中的一种。
【输出格式】
对于每个询问指令”Q1 a b”,如果a和b在同一个连通块中,则输出“Yes”,否则输出“No”。
对于每个询问指令“Q2 a”,输出一个整数表示点a所在连通块中点的数量
每个结果占一行。
【数据范围】
1≤n,m≤105
【输入样例】
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
【输出样例】
Yes
2
3
【代码】
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100010;
int p[N];
int ps[N];
int n, m;
int find(int x){
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
void lianjie(int x, int y){
int a = find(x);
int b = find(y);
p[a] = b;
ps[b] += ps[a];
}
int main(){
cin >> n >> m;
for(int i = 1; i <= n; ++i){
p[i] = i;
ps[i] = 1;
}
while(m --){
string s;
cin >> s;
int a, b;
if(s == "C"){
cin >> a >> b;
lianjie(a, b);
}
else if (s == "Q1"){
cin >> a >> b;
if( find(a ) == find(b)) {
cout << "Yes" << endl;
}
else cout << "No" << endl;
}
else{
cin >> a;
cout << ps[find(a)] <<endl;
}
}
return 0;
}