POJ——3984
题意:给定一个5*5的01矩阵,0能走,1不能走,打印出从左上角走到右下角的最短路径
题解:关于要打印路径的bfs,可在声明的struct结构中加入stepx和stepy两个记录路径的数组
#include<iostream>
#include<queue>
#include<string>
using namespace std;
int m[5][5], v[5][5];
struct node {
int x, y;
int t;
int sx[30], sy[30];
};
const int go[4][2] = { {0,1},{0,-1},{-1,0},{1,0} };
void bfs()
{
queue<node>q;
node s;
s.x = 0; s.y = 0; s.t = 0;
q.push(s);
memset(v, 0, sizeof(v));
v[0][0] = 1;
while (!q.empty()) {
node c, n;
c = q.front();
n = c;
q.pop();
if (c.x == 4 && c.y == 4) {
int i;
cout << "(0, 0)" << endl;
for (i = 1; i <= c.t; i++) {
printf("(%d, %d)\n", c.sx[i], c.sy[i]);
}
return;
}
int i;
for (i = 0; i < 4; i++) {
n.x = c.x + go[i][0];
n.y = c.y + go[i][1];
if (n.x < 0 || n.x>4 || n.y < 0 || n.y>4 || v[n.x][n.y] || m[n.x][n.y] == 1)
continue;
n.t = c.t + 1;
n.sx[n.t] = n.x;
n.sy[n.t] = n.y;
v[n.x][n.y] = 1;
q.push(n);
}
}
cout << -1 << endl;
}
int main()
{
int i, j;
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
cin >> m[i][j];
}
}
bfs();
return 0;
}
洛谷P3367
题意:有从1开始的联系N个数,通过输入使一些元素所属集合合并,判断输入两个数是否属于同一个集合
题解:并查集,注意join函数里不要写成pre[x]=y而要写成pre[find(x)]=find(y),因为如果是前面的情况,假如先让1、3合并,再让1、4合并,然而find(3)不会等于find(4)
#include<iostream>
using namespace std;
const int MAX = 1e4 + 5;
int pre[MAX];
int n, m;
void init()
{
for (int i = 0; i <= n; i++)
pre[i] = i;
}
int find(int x)
{
if (pre[x] == x)return x;
return pre[x] = find(pre[x]);
}
inline void join(int x, int y)
{
if (find(x) == find(y))
return;
pre[find(x)] = find(y);
}
int main()
{
cin >> n >> m;
init();
int z, x, y;
for (int i = 0; i < m; i++) {
cin >> z >> x >> y;
if (z == 1)
join(x, y);
if (z == 2) {
if (find(x) == find(y))
cout << "Y" << endl;
else
cout << "N" << endl;
}
}
return 0;
}
洛谷P1551
与上一题相似,直接上代码
#include<iostream>
using namespace std;
const int MAX = 1e4 + 5;
int pre[MAX];
int n, m;
void init()
{
for (int i = 0; i <= n; i++)
pre[i] = i;
}
int find(int x)
{
if (pre[x] == x)return x;
return pre[x] = find(pre[x]);
}
inline void join(int x, int y)
{
if (find(x) == find(y))
return;
pre[find(x)] = find(y);
}
int main()
{
int p;
cin >> n >> m >> p;
init();
int i;
int x, y;
for (i = 0; i < m; i++) {
cin >> x >> y;
join(x, y);
}
for (i = 0; i < p; i++) {
cin >> x >> y;
if (find(x) == find(y))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}
POJ——2236
题意:有编号从1开始的连续n台电脑(每台都有位置坐标),全部都是损坏状态,现对其一个一个进行修复,以已被修复为前提,两台电脑距离小于d即可相互通信,且通信可传递(a与b可,b与c可,则a与c可),给出两台电脑,判断能否通信
题解:并查集,用pair<int,int>tr[MAX]记录电脑坐标,在join之前判断是否被修好和距离即可
#include<iostream>
#include<string>
using namespace std;
typedef long long ll;
const int MAX = 1e3 + 5;
int pre[MAX];
pair<int, int>tr[MAX];
int n, d;
int v[MAX];
inline void init()
{
for (int i = 1; i <= n; i++)
pre[i] = i;
}
inline int find(int x)
{
return x == pre[x] ? x : (pre[x] = find(pre[x]));
}
inline void join(int x, int y)
{
pre[find(x)] = find(y);
}
int check(int x, int y)
{
int dx = tr[x].first - tr[y].first;
int dy = tr[x].second - tr[y].second;
int ans;
if (dx * dx + dy * dy <= d * d)
ans = 1;
else
ans = 0;
return ans;
}
int main()
{
cin >> n >> d;
init();
int i;
for (i = 1; i <= n; i++)
cin >> tr[i].first >> tr[i].second;
char w;
int p, q;
memset(v, 0, sizeof(v));
while (cin >> w) {
if (w == 'O') {
cin >> p;
v[p] = 1;
for (int j = 1; j <= n; j++) {
if (v[j] && check(p, j)) {
join(p, j);
}
}
}
if (w == 'S') {
cin >> p >> q;
if (find(p) == find(q))
cout << "SUCCESS" << endl;
else
cout << "FAIL" << endl;
}
}
return 0;
}
POJ——1611
题意:编号从0到N的学生,有几个学生组成的组,一个学生可以属于多个组,判断与0号接触或者间接接触的学生数(在同一组即接触)
题解:并查集
#include<iostream>
using namespace std;
const int MAX = 3e4 + 5;
int fa[MAX];
int n, m;
inline void init()
{
for (int i = 0; i < n; i++)
fa[i] = i;
}
int find(int x)
{
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
void join(int x, int y)
{
fa[find(x)] = find(y);
}
int main()
{
while (cin >> n >> m && n + m != 0) {
init();
int i, j;
for (i = 0; i < m; i++) {
int k;
cin >> k;
int x, y;
cin >> x;
for (j = 1; j < k; j++) {
cin >> y;
join(x, y);
}
}
int ans = 0;
for (i = 0; i < n; i++) {
if (find(i) == find(0))
ans++;
}
cout << ans << endl;
}
return 0;
}
HDU1213
题意:某人邀请他所有朋友参加酒席,他朋友中有互不认识的,先要安排座子,使全部相互认识的人一座(若A认识B,B认识C,则A认识C),求出需要的座子数
题解:并查集
#include<iostream>
#include<set>
using namespace std;
const int MAX = 3e4 + 5;
int fa[MAX];
int n, m;
inline void init()
{
for (int i = 1; i <= n; i++)
fa[i] = i;
}
inline int find(int x)
{
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
inline void join(int x, int y)
{
fa[find(x)] = find(y);
}
int main()
{
int t;
cin >> t;
while (t--) {
cin >> n >> m;
init();
int x, y, i;
for (i = 0; i < m; i++) {
cin >> x >> y;
join(x, y);
}
set<int>s;
for (i = 1; i <= n; i++)
s.insert(find(i));
cout << s.size() << endl;
}
return 0;
}
HDU3038
题意:有一个整数序列,接下来m行,每行告诉你某个子序列的总和,判断每次告诉你的总和是否错误(即与上文矛盾)
题解:
本题是带权并查集的经典模板题。
带权并查集的核心能力就是维护多个元素之间的连通以及偏移关系,甚至可以维护多个偏移关系。
而偏移量可以理解为当前结点到根结点的距离之和。其核心运算是向量运算。
设roota是a的根结点,rootb是b的根结点,v是a->b的关系值,val[x]是某结点x到其根结点的关系值(如距离)。
1. 当 roota != rootb 时
由上图可知,如果将 roota 归附于 rootb,那么依据向量运算规则可推出 roota->rootb = b->rootb - a->roota + a->b
由于 val[x] 数组存储的是某结点x到其根结点的关系,而roota的根节点是rootb,所以有等价表示 roota->rootb = val[roota]。相应的,有 b->rootb = val[b]、a->roota = val[a],则式子 roota->rootb = b->rootb - a->roota + a->b 可表示为:val[roota] = -val[a]+val[b]+v
由于问题HDU 3038使用的都是闭区间,闭区间会加上端点的值,这就提醒我们这个题在处理的时候应该将闭区间的某一端变成开区间,比如将[1,4]变成(0,4],将[3,4]变成(2,4],将[1,2]变成(0,2]等,即左开右闭。
2. 当 roota == rootb 时
由上图可知, a和b的根结点相同。所以我们只需验证 a->b是否与题目中给定的值一致。
显然,依据向量运算规则可推出 a->b = a->root - b->root,等价表示为 v = val[a] - val[b]
————————————————
版权声明:本文为CSDN博主「hnjzsyjyj」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hnjzsyjyj/article/details/120211514
#include<iostream>
using namespace std;
const int MAX = 2e5 + 5;
int fa[MAX], v[MAX];
int n, m;
inline void init()
{
for (int i = 0; i <= n; i++) {
fa[i] = i;
v[i] = 0;
}
}
int find(int x)
{
if (x != fa[x]) {
int t = fa[x];
fa[x] = find(t);
v[x] += v[t];
}
return fa[x];
}
int main()
{
while (cin >> n >> m){
init();
int x, y, s, i;
int ans = 0;
for (i = 0; i < m; i++) {
cin >> x >> y >> s;
x--;
int fx = find(x);
int fy = find(y);
if (fx != fy) {
fa[fx] = fy;
v[fx] = s + v[y] - v[x];
}
else {
if (v[x] - v[y] != s)
ans++;
}
}
cout << ans << endl;
}
return 0;
}
POJ——1182食物链
题意:有三个物种ABC,A吃B,B吃C,C吃A;判断每行给出的捕食关系是否有矛盾
题解:带权并查集,跟上一题不同的是,v数组不再表示字面上的数字意义,而是0代表x和y是同类,1代表x被y吃,2代表x吃y,在此基础上修改部分代码
#include<iostream>
using namespace std;
const int MAX = 5e4 + 5;
int n, k;
int fa[MAX], v[MAX];
void init()
{
for (int i = 1; i <= n; i++) {
fa[i] = i;
v[i] = 0;
}
}
inline int find(int x)
{
while (fa[x] != fa[fa[x]]) {
v[x] = (v[x] + v[fa[x]]) % 3;
fa[x] = fa[fa[x]];
}
return fa[x];
}
void join(int x, int y, int d)
{
int fx = find(x);
int fy = find(y);
if (fx == fy)
return;
fa[fx] = fy;
v[fx] = (-v[x] + 4 - d + v[y]) % 3;
}
int main()
{
cin >> n >> k;
init();
int d, x, y;
int i;
int ans = 0;
for (i = 0; i < k; i++) {
scanf_s("%d%d%d", &d, &x, &y);
if (x<0 || x>n || y<0 || y>n) {
ans++;
continue;
}
if (find(x) == find(y)) {
if (d == 1 && v[x] != v[y])
ans++;
if (d == 2 && (v[x] + 1) % 3 != v[y])
ans++;
}
else
join(x, y, d);
}
printf("%d\n", ans);
return 0;
}
关于hdu3038和poj1182,有关于带权并查集的总结文章:
HDU1241
题意:给定字符型的二维数组,‘@’代表出油口,每个出油口与其相邻的八个方位视为相连,全部相连的出油口视为一个油田,求出油田数量
题解:简单dfs,先遍历map数组,找到‘@’后以它为起点开始dfs,dfs的过程中将‘@’修改为‘#’以确保不会重复计数
#include<iostream>
using namespace std;
char map[105][105];
int m, n;
const int g[8][2] = { {1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1} };
void dfs(int x, int y)
{
if (map[x][y] != '@' || x < 0 || x >= m || y < 0 || y >= n)
return;
map[x][y] = '#';
int i;
for (i = 0; i < 8; i++) {
int nx = x + g[i][0];
int ny = y + g[i][1];
dfs(nx, ny);
}
}
int main()
{
while (cin >> m >> n && m) {
int i, j;
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
cin >> map[i][j];
}
}
int ans = 0;
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
if (map[i][j] == '@') {
dfs(i, j);
ans++;
}
}
}
cout << ans << endl;
}
return 0;
}
HDU2612
题意:给定一个地图,‘Y’代表一个起点,‘M’代表另一个起点,‘@’为终点,有多个终点且每个终点都能从两个起点到达,求两个起点到达同一个终点的最小步数和
题解:两次bfs,用一个二维数组kfc记录相应坐标上从起点到达该位置的步数之和,遍历kfc数组并找出最小值即可,注意在此题中bfs的退出条件只有!q.empty()一个(因为有多个终点,要记录从一个起点到每个终点的步数,若到第一个终点就退出那么后面的终点就不能记录相应的值了)
#include<iostream>
#include<queue>
#include<string>
#include<algorithm>
using namespace std;
int n, m;
const int g[4][2] = { {1,0},{-1,0},{0,1},{0,-1} };
char map[205][205];
int v[205][205];
int kfc[205][205];
struct node {
int x, y;
int t;
};
void bfs(node s)
{
memset(v, 0, sizeof(v));
queue<node>q;
q.push(s);
v[s.x][s.y] = 1;
while (!q.empty()) {
node cur, nex;
cur = q.front();
q.pop();
if (map[cur.x][cur.y] == '@') {
kfc[cur.x][cur.y] += cur.t;
}
int i;
for (i = 0; i < 4; i++) {
nex.x = cur.x + g[i][0];
nex.y = cur.y + g[i][1];
nex.t = cur.t + 1;
if (nex.x < 0 || nex.x >= n || nex.y < 0 || nex.y >= m || v[nex.x][nex.y] || map[nex.x][nex.y] == '#')
continue;
v[nex.x][nex.y] = 1;
q.push(nex);
}
}
}
int main()
{
while (cin >> n >> m) {
int i, j;
node Y, M;
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
cin >> map[i][j];
if (map[i][j] == 'Y') {
Y.x = i;
Y.y = j;
Y.t = 0;
}
if (map[i][j] == 'M') {
M.x = i;
M.y = j;
M.t = 0;
}
}
}
int ans = 1e7;
memset(kfc, 0, sizeof(kfc));
bfs(Y);
bfs(M);
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
if (kfc[i][j] > 0) {
ans = min(ans, kfc[i][j]);
}
}
}
cout << ans * 11 << endl;
}
return 0;
}
POJ——1962
题意:编号从1到N的公司,每个公司都有自己的通讯电脑,有两种操作,一是输入A和B,A是某个通讯群的中心,B没要求,将B作为A的通讯中心;二是查询某个公司到其通讯中心的距离
题解:带权并查集,注意A的父节点是它本身
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int MAX = 2e4 + 5;
int fa[MAX], v[MAX];
int n;
inline void init()
{
for (int i = 1; i <= n; i++) {
fa[i] = i;
v[i] = 0;
}
}
int find(int x)
{
if (x != fa[x]) {
int t = fa[x];
fa[x] = find(fa[x]);
v[x] = v[x] + v[t];
}
return fa[x];
}
int main()
{
int t;
cin >> t;
while (t--) {
cin >> n;
init();
char w;
while (cin >> w && w != 'O') {
int x, y;
if (w == 'E') {
cin >> x;
find(x);
cout << v[x] << endl;
}
if (w == 'I') {
cin >> x >> y;
int dis = abs(x - y) % 1000;
fa[x] = y;
v[x] = dis;
}
}
}
return 0;
}
POJ——1703
题意:一个城市有两个帮派,且每个帮派都至少有一个人,有从1到N的罪犯,他们都属于其中一个帮派,输入有两种操作,一是A和B是不同帮派;二是判断A和B是否属于同一个帮派
题解:食物链的简单版本,带权并查集,注意输入要用scanf,否则会TLE
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int MAX = 1e5 + 5;
int fa[MAX], v[MAX];
int n, m;
inline void init()
{
for (int i = 1; i <= n; i++) {
fa[i] = i;
v[i] = 0;
}
}
int find(int x)
{
if (x != fa[x]) {
int t = fa[x];
fa[x] = find(fa[x]);
v[x] = (v[x] + v[t] + 2) % 2;
}
return fa[x];
}
int main()
{
int t;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
getchar();
init();
char w;
int i, x, y;
for (i = 0; i < m; i++) {
scanf("%c%d%d", &w, &x, &y);
getchar();
int fx = find(x);
int fy = find(y);
if (w == 'A') {
if (fx != fy)
cout << "Not sure yet." << endl;
else {
if (v[x] == v[y])
cout << "In the same gang." << endl;
else
cout << "In different gangs." << endl;
}
}
else {
fa[fx] = fy;
v[fx] = (-v[x] + 1 + v[y]) % 2;
}
}
}
return 0;
}
POJ——2492
题意:有一种昆虫,先要判断他们是否只与异性交配,输入一些列A和B表示A和B能相互交配,判断是否发现可疑个体(即跟之前的输入矛盾)
题解:带权并查集,与上一题类似
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int MAX = 1e5 + 5;
int fa[MAX], v[MAX];
int n, m;
inline void init()
{
for (int i = 1; i <= n; i++) {
fa[i] = i;
v[i] = 0;
}
}
int find(int x)
{
if (x != fa[x]) {
int t = fa[x];
fa[x] = find(fa[x]);
v[x] = (v[x] + v[t] + 2) % 2;
}
return fa[x];
}
int main()
{
int t;
scanf("%d", &t);
int k;
for (k = 1; k <= t; k++) {
scanf("%d%d", &n, &m);
getchar();
init();
int i;
int x, y;
bool ans = true;
for (i = 0; i < m; i++) {
cin >> x >> y;
int fx = find(x);
int fy = find(y);
if (fx == fy && v[x] == v[y])
ans = false;
if (fx != fy) {
fa[fx] = fy;
v[fx] = (-v[x] + 1 + v[y]) % 2;
}
}
printf("Scenario #%d:\n", k);
if (ans)
printf("No suspicious bugs found!\n\n");
else
printf("Suspicious bugs found!\n\n");
}
return 0;
}