This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 200 * 200 + 10;
int n, m;
int p[N];
// 二维转一维的办法,坐标从(1,1)开始
int get(int x, int y) {
return (x - 1) * n + y;
}
// 最简并查集
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); // 路径压缩
return p[x];
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n * n; i++) p[i] = i; // 并查集初始化
int res = 0;
for (int i = 1; i <= m; i++) {
int x, y;
char d;
cin >> x >> y >> d;
int a = get(x, y); // 计算a点点号
int b;
if (d == 'D') // 向下走
b = get(x + 1, y);
else // 向右走
b = get(x, y + 1);
// a,b需要两次相遇,才是出现了环~
int pa = find(a), pb = find(b);
if (pa == pb) {
res = i; // 记录操作步数
break;
}
// 合并并查集
p[pa] = pb;
}
if (!res) // 没有修改过这个值
puts("draw"); // 平局
else // 输出操作步数
printf("%d\n", res);
return 0;
}

View File

@@ -0,0 +1,103 @@
##[$AcWing$ $1250$. 格子游戏](https://www.acwing.com/problem/content/description/1252/)
### 一、题目描述
$Alice$和$Bob$玩了一个古老的游戏:首先画一个 $n×n$ 的点阵(下图 $n=3$ )。
接着,他们两个轮流在相邻的点之间画上红边和蓝边:
<center><img src='https://cdn.acwing.com/media/article/image/2019/12/11/19_9edbcf521b-1.png'></center>
直到围成一个封闭的圈(面积不必为 $1$)为止,“封圈”的那个人就是赢家。因为棋盘实在是太大了,他们的游戏实在是太长了!
他们甚至在游戏中都不知道谁赢得了游戏。
于是请你写一个程序,帮助他们计算他们是否结束了游戏?
**输入格式**
输入数据第一行为两个整数 $n$ 和 $m$。$n$表示点阵的大小,$m$ 表示一共画了 $m$ 条线。
以后 $m$ 行,每行首先有两个数字 ($x,y$),代表了画线的起点坐标,接着用空格隔开一个字符,假如字符是 $D$,则是向下连一条边,如果是 $R$ 就是向右连一条边。
输入数据不会有重复的边且保证正确。
**输出格式**
输出一行:在第几步的时候结束。
假如 $m$ 步之后也没有结束,则输出一行“$draw$”。
**数据范围**
$1≤n≤200,
1≤m≤24000$
**输入样例**:
```cpp {.line-numbers}
3 5
1 1 D
1 1 R
1 2 D
2 1 R
2 2 D
```
**输出样例**:
```cpp {.line-numbers}
4
```
### 二、解题思路
**判断是否成环,可以判断他们没连接前,他们的祖宗结点是否一致,如果一致,连接起来就必然成环。**
### 三、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 200 * 200 + 10;
int n, m;
int p[N];
// 二维转一维的办法,坐标从(1,1)开始
int get(int x, int y) {
return (x - 1) * n + y;
}
// 最简并查集
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); // 路径压缩
return p[x];
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n * n; i++) p[i] = i; // 并查集初始化
int res = 0;
for (int i = 1; i <= m; i++) {
int x, y;
char d;
cin >> x >> y >> d;
int a = get(x, y); // 计算a点点号
int b;
if (d == 'D') // 向下走
b = get(x + 1, y);
else // 向右走
b = get(x, y + 1);
// a,b需要两次相遇,才是出现了环~
int pa = find(a), pb = find(b);
if (pa == pb) {
res = i; // 记录操作步数
break;
}
// 合并并查集
p[pa] = pb;
}
if (!res) // 没有修改过这个值
puts("draw"); // 平局
else // 输出操作步数
printf("%d\n", res);
return 0;
}
```

View File

@@ -0,0 +1,48 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m, sum; // 有 n 朵云,m 个搭配,Joe有 sum 的钱。
int v[N], w[N]; // 表示 i 朵云的价钱和价值
int p[N]; // 并查集数组
int f[N]; // 01背包数组
// 最简并查集
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); // 路径压缩
return p[x];
}
int main() {
scanf("%d %d %d", &n, &m, &sum);
// 初始化并查集
for (int i = 1; i <= n; i++) p[i] = i;
// 读入每个云朵的价钱(体积)和价值
for (int i = 1; i <= n; i++)
scanf("%d %d", &v[i], &w[i]);
while (m--) {
int a, b;
cin >> a >> b; // 两种云朵需要一起买
int pa = find(a), pb = find(b);
if (pa != pb) {
// 集合有两个属性:总价钱、总价值,都记录到root节点上
v[pb] += v[pa];
w[pb] += w[pa];
p[pa] = pb;
}
}
// 01背包
// 注意:这里不能认为一维的能AC,二维的替代写法就一定能AC
// 这是因为这里的判断p[i]==i,导致i不一定是连续的,
// 所以f[i][j]=f[i-1][j]这句话就不一定对
// 所以,看来终极版本的01背包一维解法还是有一定价值的。
for (int i = 1; i <= n; i++)
if (p[i] == i) // 只关心集合代表元素,选择一组
for (int j = sum; j >= v[i]; j--) // 体积由大到小,倒序,01背包
f[j] = max(f[j], f[j - v[i]] + w[i]);
// 输出最大容量下获取到的价值
printf("%d\n", f[sum]);
return 0;
}

View File

@@ -0,0 +1,171 @@
##[$AcWing$ $1252$. 搭配购买](https://www.acwing.com/problem/content/description/1254/)
### 一、题目描述
$Joe$觉得云朵很美,决定去山上的商店买一些云朵。
商店里有 $n$ 朵云,云朵被编号为 $1,2,…,n$,并且每朵云都有一个价值。
但是商店老板跟他说,一些云朵要搭配来买才好,所以买一朵云则与这朵云有搭配的云都要买。
但是$Joe$的钱有限,所以**他希望买的价值越多越好**。
**输入格式**
第 $1$ 行包含三个整数 $n,m,w$,表示有 $n$ 朵云,$m$ 个搭配,$Joe$有 $w$ 的钱。
第 $2∼n+1$行,每行两个整数 $c_i,d_i$ 表示 $i$ 朵云的价钱和价值。
第 $n+2∼n+1+m$ 行,每行两个整数 $u_i,v_i$,表示买 $u_i$ 就必须买 $v_i$,同理,如果买 $v_i$ 就必须买 $u_i$。
**输出格式**
一行,表示可以获得的最大价值。
**数据范围**
$1≤n≤10000,0≤m≤5000,1≤w≤10000,1≤ci≤5000,1≤di≤100,1≤u_i,v_i≤n$
**输入样例**:
```cpp {.line-numbers}
5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2
```
**输出样例**:
```cpp {.line-numbers}
1
```
### 二、解题思路
![](https://img2022.cnblogs.com/blog/8562/202204/8562-20220412102228070-811700211.png)
### 三、一维$01$背包解法
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m, sum; // 有 n 朵云,m 个搭配,Joe有 sum 的钱。
int v[N], w[N]; // 表示 i 朵云的价钱和价值
int p[N]; // 并查集数组
int f[N]; // 01背包数组
// 最简并查集
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); // 路径压缩
return p[x];
}
int main() {
scanf("%d %d %d", &n, &m, &sum);
// 初始化并查集
for (int i = 1; i <= n; i++) p[i] = i;
// 读入每个云朵的价钱(体积)和价值
for (int i = 1; i <= n; i++)
scanf("%d %d", &v[i], &w[i]);
while (m--) {
int a, b;
cin >> a >> b; // 两种云朵需要一起买
int pa = find(a), pb = find(b);
if (pa != pb) {
// 集合有两个属性:总价钱、总价值,都记录到root节点上
v[pb] += v[pa];
w[pb] += w[pa];
p[pa] = pb;
}
}
// 01背包
// 注意:这里不能认为一维的能AC,二维的替代写法就一定能AC
// 这是因为这里的判断p[i]==i,导致i不一定是连续的,
// 所以f[i][j]=f[i-1][j]这句话就不一定对
// 所以,看来终极版本的01背包一维解法还是有一定价值的。
for (int i = 1; i <= n; i++)
if (p[i] == i) // 只关心集合代表元素,选择一组
for (int j = sum; j >= v[i]; j--) // 体积由大到小,倒序,01背包
f[j] = max(f[j], f[j - v[i]] + w[i]);
// 输出最大容量下获取到的价值
printf("%d\n", f[sum]);
return 0;
}
```
### 四、二维$01$背包解法与不能$AC$的理解
采用二维数组的表示法,有以下两个问题:
* 本行结果不一定是从上一行推导过来,因为上一行很可能不是这个家族的族长,只有族长也有资格进行计算。
可以采用$last$变量记录的方法模拟完成二维数组的计算,具体实现见代码。
* 内存超界
过掉$7/11$个数据,无法$AC$
原因分析:$f[N][N]$第一维是可以选择的物品个数,上限是$10000$;
第二维是可以支付的钱数,上限也是$10000$;
如果按二维思路来处理,确实需要一个$10000*10000$的数组
$10000*10000*8= 800000000 byte$
$800000000/1024/1024= 762MB$ 本题上限是$64MB$,妥妥的超内存,$MLE$~
穷则思变,既然$int$+二维过不了,那么试试$short$吧,因为$short$最大是$65536$,符合题意,并且只占两个$bit$,就是$10000*10000*2= 200000000 byte$
$200000000/1024/1024= 190MB$ 本题上限是$64MB$,妥妥的超内存,$MLE$~
那么一维的为什么可以呢?
一维的只有$10000*8=80000 byte$
$80000/1024/1024=0.076MB$ 本题上限是$64MB$,肯定不会在内存上出问题。
**总结**:
**(1)$01$背包一维相比二维,能够节约非常大的空间,二维特别容易$MLE$。
(2)$01$背包一维相比二维,不用考虑上一个依赖是不是$i-1$行的问题,不用特殊用$last$方式记录并处理,出错概率小**
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m, sum; //有 n 朵云,m 个搭配,Joe有 sum 的钱。
int v[N], w[N]; //表示 i 朵云的价钱和价值
int p[N];
int f[N][N];
/*过掉7/11个数据,无法AC*/
//最简并查集
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); //路径压缩
return p[x];
}
int main() {
cin >> n >> m >> sum;
//实始化并查集
for (int i = 1; i <= n; i++) p[i] = i;
//读入每个云朵的价钱(体积)和价值
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
while (m--) {
int a, b;
cin >> a >> b; //两种云朵需要一起买
int pa = find(a), pb = find(b);
if (pa != pb) {
//集合有两个属性:总价钱、总价值,都记录到root节点上
v[pb] += v[pa];
w[pb] += w[pa];
p[pa] = pb;
}
}
// 01背包
int last = 0;
for (int i = 1; i <= n; i++)
if (p[i] == i) { //因处理集合的代表元素
for (int j = 1; j <= sum; j++) {
f[i][j] = f[last][j];
if (v[i] <= j)
f[i][j] = max(f[i][j], f[last][j - v[i]] + w[i]);
}
last = i; //依赖的上一个状态
}
printf("%d\n", f[n][sum]);
return 0;
}
```

View File

@@ -0,0 +1,70 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m, sum; //有 n 朵云,m 个搭配,Joe有 sum 的钱。
int v[N], w[N]; //表示 i 朵云的价钱和价值
int p[N];
int f[N][N];
/*过掉7/11个数据,无法AC
原因分析:f[N][N]第一维是可以选择的物品个数,上限是10000;
第二维是可以支付的钱数,上限也是10000;
如果按二维思路来处理,确实需要一个10000*10000的数组
10000*10000*8= 800000000 byte
800000000/1024/1024= 762MB 本题上限是64MB,妥妥的超内存,MLE~
穷则思变,既然int+二维过不了,那么试试short吧,因为short最大是65536,符合题意,
并且只占两个bit,就是10000*10000*2= 200000000 byte
200000000/1024/1024= 190MB 本题上限是64MB,妥妥的超内存,MLE~
那么一维的为什么可以呢?
一维的只有10000*8=80000 byte
80000/1024/1024=0.076MB 本题上限是64MB,肯定不会在内存上出问题。
总结:
(1)01背包一维相比二维,能够节约非常大的空间,二维特别容易MLE。
(2)01背包一维相比二维,不用考虑上一个依赖是不是i-1行的问题,不用特殊用last方式
记录并处理,出错概率小
*/
//最简并查集
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); //路径压缩
return p[x];
}
int main() {
cin >> n >> m >> sum;
//实始化并查集
for (int i = 1; i <= n; i++) p[i] = i;
//读入每个云朵的价钱(体积)和价值
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
while (m--) {
int a, b;
cin >> a >> b; //两种云朵需要一起买
int pa = find(a), pb = find(b);
if (pa != pb) {
//集合有两个属性:总价钱、总价值,都记录到root节点上
v[pb] += v[pa];
w[pb] += w[pa];
p[pa] = pb;
}
}
// 01背包
int last = 0;
for (int i = 1; i <= n; i++)
if (p[i] == i) { //因处理集合的代表元素
for (int j = 1; j <= sum; j++) {
f[i][j] = f[last][j];
if (v[i] <= j)
f[i][j] = max(f[i][j], f[last][j - v[i]] + w[i]);
}
last = i; //依赖的上一个状态
}
printf("%d\n", f[n][sum]);
return 0;
}

View File

@@ -0,0 +1,72 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 200010; // 因为是需要存入左右两个条件xi=xj,这样最多是保存的两倍的n
int m; // m个条件
int b[N], bl; // 离散化数组,数组长度
int p[N]; // 并查集数组
struct Node {
int x, y, e;
} a[N]; // 输入的条件
// 并查集
int find(int x) {
if (x == p[x]) return x;
return p[x] = find(p[x]);
}
// 利用二分计算出x值在已排序数组b中的位置,位置就是新的号码
int get(int x) {
int l = 1, r = bl;
while (l < r) {
int mid = (l + r) >> 1;
if (b[mid] < x)
l = mid + 1;
else
r = mid;
}
return l;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
int flag = 0, idx = 0;
scanf("%d", &m);
for (int i = 1; i <= 2 * m; i++) p[i] = i;
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &a[i].x, &a[i].y, &a[i].e);
b[idx++] = a[i].x, b[idx++] = a[i].y; // 存入离散化数组中,准备处理
}
// 静态数组离散化
sort(b, b + 2 * m);
bl = unique(b, b + 2 * m) - b;
// 相等关系 <=> 同一个并查集
for (int i = 1; i <= m; i++)
if (a[i].e == 1) {
int pa = find(get(a[i].x)), pb = find(get(a[i].y));
if (pa != pb) p[pa] = pb;
}
// 不等关系 与 同一个并查集 存在冲突
for (int i = 1; i <= m; i++)
if (a[i].e == 0) {
int pa = find(get(a[i].x)), pb = find(get(a[i].y));
if (pa == pb) {
flag = 1;
break;
}
}
if (flag)
puts("NO");
else
puts("YES");
}
return 0;
}

View File

@@ -0,0 +1,130 @@
##[$AcWing$ $237$. 程序自动分析](https://www.acwing.com/problem/content/239/)
### 一、题目描述
在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设 $x_1,x_2,x_3$,… 代表程序中出现的变量,给定 $n$ 个形如 $x_i=x_j$ 或 $x_i≠x_j$ 的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。
例如,一个问题中的约束条件为:$x_1=x_2,x_2=x_3,x_3=x_4,x_1≠x_4$,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。
**输入格式**
输入文件的第 $1$ 行包含 $1$ 个正整数 $t$,表示需要判定的问题个数,注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第 $1$ 行包含 $1$ 个正整数 $n$,表示该问题中需要被满足的约束条件个数。
接下来 $n$ 行,每行包括 $3$ 个整数 $i,j,e$,描述 $1$ 个相等/不等的约束条件,相邻整数之间用单个空格隔开。若 $e=1$,则该约束条件为 $x_i=x_j$;若 $e=0$,则该约束条件为 $x_i≠x_j$。
**输出格式**
输出文件包括 $t$ 行。
输出文件的第 $k$ 行输出一个字符串 $YES$ 或者 $NO$,$YES$ 表示输入中的第 $k$ 个问题判定为可以被满足,$NO$ 表示不可被满足。
**数据范围**
$1≤n≤10^5$
$1≤i,j≤10^9$
**输入样例**:
```cpp {.line-numbers}
2
2
1 2 1
1 2 0
2
1 2 1
2 1 1
```
**输出样例**:
```cpp {.line-numbers}
NO
YES
```
### 二、题目解析
本题目主要是学习 **离散化+二分+并查集**,原因:
本题 $1 <=i,j<=10^9$,如果描述$x_i,x_j$也就是最大描述的是$x_{1e9}$
这样没法直接使用并查集,就像是桶没法开这么大!那怎么办才好呢?
输入的个数$m$比较小($<=10^5$),可以使用离散化,然后通过二分的办法来定位一个大数在映射后数组的位置是多少。
### 三、静态数组+离散化+去重+并查集
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 200010; // 因为是需要存入左右两个条件xi=xj,这样最多是保存的两倍的n
int m; // m个条件
int b[N], bl; // 离散化数组,数组长度
int p[N]; // 并查集数组
struct Node {
int x, y, e;
} a[N]; // 输入的条件
// 并查集
int find(int x) {
if (x == p[x]) return x;
return p[x] = find(p[x]);
}
// 利用二分计算出x值在已排序数组b中的位置,位置就是新的号码
int get(int x) {
int l = 1, r = bl;
while (l < r) {
int mid = (l + r) >> 1;
if (b[mid] < x)
l = mid + 1;
else
r = mid;
}
return l;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
int flag = 0, idx = 0;
scanf("%d", &m);
for (int i = 1; i <= 2 * m; i++) p[i] = i;
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &a[i].x, &a[i].y, &a[i].e);
b[idx++] = a[i].x, b[idx++] = a[i].y; // 存入离散化数组中,准备处理
}
// 静态数组离散化
sort(b, b + 2 * m);
bl = unique(b, b + 2 * m) - b;
// 相等关系 <=> 同一个并查集
for (int i = 1; i <= m; i++)
if (a[i].e == 1) {
int pa = find(get(a[i].x)), pb = find(get(a[i].y));
if (pa != pb) p[pa] = pb;
}
// 不等关系 与 同一个并查集 存在冲突
for (int i = 1; i <= m; i++)
if (a[i].e == 0) {
int pa = find(get(a[i].x)), pb = find(get(a[i].y));
if (pa == pb) {
flag = 1;
break;
}
}
if (flag)
puts("NO");
else
puts("YES");
}
return 0;
}
```

View File

@@ -0,0 +1,46 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 30010;
int m;
int p[N], sz[N], d[N];
int find(int x) {
if (p[x] != x) {
int px = find(p[x]);
d[x] += d[p[x]]; // 距离
p[x] = px;
}
return p[x];
}
int main() {
scanf("%d", &m);
for (int i = 1; i < N; i++) p[i] = i, sz[i] = 1;
while (m--) {
char op[2]; // 读入一个操作符的办法:读入一个字符串,只用op[0]
scanf("%s", op);
int a, b;
scanf("%d %d", &a, &b);
if (op[0] == 'M') {
int pa = find(a), pb = find(b);
if (pa != pb) { // 如果a,b不在同一个集合中,需要合并集合
d[pa] = sz[pb]; // 需要在它家族人数未修改前,记录下pa前面的船的数量,否则,修改完就没机会了
sz[pb] += sz[pa]; // 维护pb家族人员数量
p[pa] = pb; // 将pa加入pb家族
}
} else {
int pa = find(a), pb = find(b);
if (pa != pb) // 不在一起
puts("-1");
else
// 注意:题目C操作问的是间隔多少条船,若a != b,
// 则间隔abs(d[a] - d[b]) - 1条船,若a == b,则间隔0条船
cout << max(0, abs(d[a] - d[b]) - 1) << endl;
}
}
return 0;
}

View File

@@ -0,0 +1,106 @@
##[$AcWing$ $238$. 银河英雄传说](https://www.acwing.com/problem/content/240/)
### 一、题目描述
有一个划分为 $N$ 列的星际战场,各列依次编号为 $1,2,…,N$。
有 $N$ 艘战舰,也依次编号为 $1,2,…,N$,其中第 $i$ 号战舰处于第 $i$列。
有 $T$ 条指令,每条指令格式为以下两种之一:
`M i j`,表示让第 $i$号战舰所在列的全部战舰保持原有顺序,接在第 $j$号战舰所在列的尾部。
`C i j`,表示询问第 $i$ 号战舰与第 $j$ 号战舰当前是否处于同一列中,**如果在同一列中,它们之间间隔了多少艘战舰**。
现在需要你编写一个程序,处理一系列的指令。
**输入格式**
第一行包含整数 $T$,表示共有 $T$条指令。
接下来 $T$行,每行一个指令,指令有两种形式:`M i j` 或 `C i j`。
其中 $M$ 和 $C$ 为大写字母表示指令类型,$i$ 和 $j$ 为整数,表示指令涉及的战舰编号。
**输出格式**
你的程序应当依次对输入的每一条指令进行分析和处理:
如果是 `M i j` 形式,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息;
如果是 `C i j` 形式,你的程序要输出一行,仅包含一个整数,表示在同一列上,第 $i$
号战舰与第 $j$ 号战舰之间布置的战舰数目,如果第 $i$ 号战舰与第 $j$ 号战舰当前不在同一列上,则输出 $−1$。
**数据范围**
$N≤30000,T≤500000$
**输入样例**:
```cpp {.line-numbers}
4
M 2 3
C 1 2
M 2 4
C 4 2
```
**输出样例**:
```cpp {.line-numbers}
-1
1
```
### 二、解题思路
* 并查集除可以维护集合异同关系外,还可维护集合中元素数量
* 除了维护集合中元素的数量,还同时需要维护距离根的距离长度
[姊妹题 $POJ-1988-Cube Stacking$](https://www.cnblogs.com/littlehb/p/16128501.html)
### 三、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 30010;
int m;
int p[N], sz[N], d[N];
int find(int x) {
if (p[x] != x) {
int px = find(p[x]);
d[x] += d[p[x]]; // 距离
p[x] = px;
}
return p[x];
}
int main() {
scanf("%d", &m);
for (int i = 1; i < N; i++) p[i] = i, sz[i] = 1;
while (m--) {
char op[2]; // 读入一个操作符的办法:读入一个字符串,只用op[0]
scanf("%s", op);
int a, b;
scanf("%d %d", &a, &b);
if (op[0] == 'M') {
int pa = find(a), pb = find(b);
if (pa != pb) { // 如果a,b不在同一个集合中,需要合并集合
d[pa] = sz[pb]; // 需要在它家族人数未修改前,记录下pa前面的船的数量,否则,修改完就没机会了
sz[pb] += sz[pa]; // 维护pb家族人员数量
p[pa] = pb; // 将pa加入pb家族
}
} else {
int pa = find(a), pb = find(b);
if (pa != pb) // 不在一起
puts("-1");
else
// 注意:题目C操作问的是间隔多少条船,若a != b,
// 则间隔abs(d[a] - d[b]) - 1条船,若a == b,则间隔0条船
cout << max(0, abs(d[a] - d[b]) - 1) << endl;
}
}
return 0;
}
```

View File

@@ -0,0 +1,331 @@
##[$AcWing$ $239$ . 奇偶游戏](https://www.acwing.com/problem/content/241/)
### 一、题目描述
小 $A$ 和小 $B$ 在玩一个游戏。
首先,小 $A$ 写了一个由 $0$ 和 $1$ 组成的序列 $S$,长度为 $N$。
然后,小 $B$ 向小 $A$ 提出了 $M$ 个问题。
在每个问题中,小 $B$ 指定两个数 $l$ 和 $r$,小 $A$ 回答 $S[l∼r]$ 中有奇数个 $1$ 还是偶数个 $1$。
机智的小 $B$ 发现小 $A$ 有可能在撒谎。
例如,小 $A$ 曾经回答过 $S[1∼3]$ 中有奇数个 $1$,$S[4∼6]$ 中有偶数个 $1$,现在又回答 $S[1∼6]$ 中有偶数个 $1$,显然这是自相矛盾的。
请你帮助小 $B$ 检查这 $M$ 个答案,并指出在至少多少个回答之后可以确定小 $A$ 一定在撒谎。
即求出一个最小的 $k$,使得 $01$ 序列 $S$ 满足第 $1∼k$ 个回答,但不满足第 $1∼k+1$ 个回答。
**输入格式**
第一行包含一个整数 $N$,表示 $01$ 序列长度。
第二行包含一个整数 $M$,表示问题数量。
接下来 $M$ 行,每行包含一组问答:两个整数 $l$ 和 $r$,以及回答 $even$ 或 $odd$,用以描述 $S[l∼r]$ 中有偶数个 $1$ 还是奇数个 $1$。
**输出格式**
输出一个整数 $k$,表示 $01$ 序列满足第 $1∼k$ 个回答,但不满足第 $1∼k+1$ 个回答,如果 $01$ 序列满足所有回答,则输出问题总数量。
**数据范围**
$N≤10^9,M≤5000$
**输入样例**:
```cpp {.line-numbers}
10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd
```
**输出样例**:
```cpp {.line-numbers}
3
```
### 二、题目解析
**前缀和**
* 如果区间$[l, r]$有偶数个$1$,那么$s[r]$和$s[l-1]$的奇偶性一定相同,因为偶数-偶数=偶数
* 如果区间$[l, r]$有奇数个$1$,那么$s[r]$和$s[l-1]$的奇偶性一定不同,因为偶数-奇数=奇数,或者,奇数-奇数=偶数
这样一来,维护区间信息就变成维护俩端点的信息了。
往广义的来说,**并查集维护的是俩俩元素之间的信息**。(这个信息,可以是 **是否联通** ,也可以是 **奇偶性是否相同** ,还可以是 **两点距离** 等等)
对于这一题,并查集维护的是 **俩元素间的奇偶性关系**,$d[x]$表示$x$点与父亲的关系,$0$代表奇偶性相同,$1$代表奇偶性不同。那么显然每个点与根的奇偶关系就可以通过做路径上的边权做一遍异或即可。
### 三、带权并查集+$STL$离散化
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 20010;
int n, m;
int p[N], d[N];
// 无序离散化
unordered_map<int, int> S;
int get(int x) {
if (S[x] == 0) S[x] = ++n; // x映射为第n个数字
return S[x];
}
// 带边权更新并查集模板
int find(int x) {
if (x == p[x]) return x;
int root = find(p[x]);
d[x] += d[p[x]];
return p[x] = root;
}
int main() {
scanf("%d %d", &n, &m);
n = 0; // 序列的长度没有用处,我们只关心每个a,b范围内的数字1的个数
for (int i = 1; i < N; i++) p[i] = i; // 初始化并查集
int res = m;
for (int i = 1; i <= m; i++) {
int a, b;
// a~b之间1是奇数个还是偶数个
scanf("%d %d", &a, &b);
char type[100]; // 字符数组
scanf("%s", type);
// 类前缀和
a = get(a - 1), b = get(b);
int t = 0; // 偶数个1
if (type[0] == 'o') t = 1; // 奇数个1
// 并查集
int pa = find(a), pb = find(b);
if (pa == pb) {
if (abs(d[a] - d[b]) % 2 != t) {
res = i - 1; // 最后一条正确的序号
break;
}
} else {
p[pa] = pb;
d[pa] = abs(d[a] - d[b] - t) % 2;
}
}
printf("%d\n", res);
return 0;
}
```
### 四、带权并查集+静态数组+二分离散化
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 20010;
// 结构体记录原始输入
struct Node {
int x, y, e;
} g[N];
int n, m;
// 离散化静态数组+二分查找新位置
int b[N], bl;
int get(int x) {
return lower_bound(b, b + bl, x) - b;
}
// 带边权更新并查集模板
int p[N], d[N];
int find(int x) {
if (x == p[x]) return x;
int root = find(p[x]);
d[x] += d[p[x]];
return p[x] = root;
}
int main() {
scanf("%d %d", &n, &m);
n = 0; // 序列的长度没有用处,我们只关心每个a,b范围内的数字1的个数
for (int i = 1; i < N; i++) p[i] = i; // 初始化并查集
for (int i = 1; i <= m; i++) {
int x, y;
char t[100];
scanf("%d %d %s", &x, &y, t);
g[i].x = x, g[i].y = y;
if (t[0] == 'e')
g[i].e = 0;
else
g[i].e = 1;
// 记录下来
b[bl++] = x, b[bl++] = y;
}
// 离散化去重
sort(b, b + 2 * m);
bl = unique(b, b + 2 * m) - b;
int res = m;
for (int i = 1; i <= m; i++) {
int a = g[i].x, b = g[i].y, e = g[i].e;
// 类前缀和
a = get(a - 1), b = get(b);
int t = 0; // 偶数个1
if (e == 1) t = 1; // 奇数个1
// 并查集
int pa = find(a), pb = find(b);
if (pa == pb) {
if (abs(d[a] - d[b]) % 2 != t) {
res = i - 1; // 最后一条正确的序号
break;
}
} else {
p[pa] = pb;
d[pa] = abs(d[a] - d[b] - t) % 2;
}
}
printf("%d\n", res);
return 0;
}
```
### 五、扩展域+$STL$离散化+并查集
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 40010, B = N / 2;
// 简化版本的食物链
int n, m;
// 无序离散化
unordered_map<int, int> S;
int get(int x) {
if (S[x] == 0) S[x] = ++n;
return S[x];
}
// 并查集
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d %d", &n, &m);
n = 0;
for (int i = 1; i < N; i++) p[i] = i;
int res = m;
for (int i = 1; i <= m; i++) {
int a, b;
scanf("%d %d", &a, &b);
char t[100];
scanf("%s", t);
a = get(a - 1), b = get(b); // 计算出新的在并查集中的号
if (t[0] == 'e') { // 偶数个1
if (find(a + B) == find(b)) { // 如果奇偶性不同,因为b与a+B相同
res = i - 1;
break;
}
// join两个奇偶相同的集合
p[find(a)] = find(b);
p[find(a + B)] = find(b + B);
} else { // 奇数个1
if (find(a) == find(b)) {
res = i - 1;
break;
}
// join两个奇偶不相同的集合
p[find(a + B)] = find(b);
p[find(a)] = find(b + B);
}
}
printf("%d\n", res);
return 0;
}
```
### 六、静态数组离散化+二分+扩展域并查集
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 40010, B = N / 2;
// 结构体记录原始输入
struct Node {
int x, y, e;
} g[N];
int n, m;
// 离散化静态数组+二分查找新位置
int b[N], bl;
int get(int x) {
return lower_bound(b, b + bl, x) - b;
}
// 并查集
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d %d", &n, &m);
n = 0;
for (int i = 1; i < N; i++) p[i] = i;
for (int i = 1; i <= m; i++) {
int x, y;
char t[100];
scanf("%d %d %s", &x, &y, t);
g[i].x = x, g[i].y = y;
if (t[0] == 'e')
g[i].e = 0;
else
g[i].e = 1;
// 记录下来
b[bl++] = x, b[bl++] = y;
}
// 离散化去重
sort(b, b + 2 * m);
bl = unique(b, b + 2 * m) - b;
int res = m;
for (int i = 1; i <= m; i++) {
int a = g[i].x, b = g[i].y, e = g[i].e;
a = get(a - 1), b = get(b); // 计算出新的在并查集中的号
if (e == 0) { // 偶数个1
if (find(a + B) == find(b)) { // 如果奇偶性不同,因为b与a+B相同
res = i - 1;
break;
}
// join两个奇偶相同的集合
p[find(a)] = find(b);
p[find(a + B)] = find(b + B);
} else { // 奇数个1
if (find(a) == find(b)) {
res = i - 1;
break;
}
// join两个奇偶不相同的集合
p[find(a + B)] = find(b);
p[find(a)] = find(b + B);
}
}
printf("%d\n", res);
return 0;
}
```

View File

@@ -0,0 +1,56 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 20010;
int n, m;
int p[N], d[N];
// 无序离散化
unordered_map<int, int> S;
int get(int x) {
if (S[x] == 0) S[x] = ++n; // x映射为第n个数字
return S[x];
}
// 带边权更新并查集模板
int find(int x) {
if (x == p[x]) return x;
int root = find(p[x]);
d[x] += d[p[x]];
return p[x] = root;
}
int main() {
scanf("%d %d", &n, &m);
n = 0; // 序列的长度没有用处,我们只关心每个a,b范围内的数字1的个数
for (int i = 1; i < N; i++) p[i] = i; // 初始化并查集
int res = m;
for (int i = 1; i <= m; i++) {
int a, b;
// a~b之间1是奇数个还是偶数个
scanf("%d %d", &a, &b);
char type[100]; // 字符数组
scanf("%s", type);
// 类前缀和
a = get(a - 1), b = get(b);
int t = 0; // 偶数个1
if (type[0] == 'o') t = 1; // 奇数个1
// 并查集
int pa = find(a), pb = find(b);
if (pa == pb) {
if (abs(d[a] - d[b]) % 2 != t) {
res = i - 1; // 最后一条正确的序号
break;
}
} else {
p[pa] = pb;
d[pa] = abs(d[a] - d[b] - t) % 2;
}
}
printf("%d\n", res);
return 0;
}

View File

@@ -0,0 +1,73 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 20010;
// 结构体记录原始输入
struct Node {
int x, y, e;
} g[N];
int n, m;
// 离散化静态数组+二分查找新位置
int b[N], bl;
int get(int x) {
return lower_bound(b, b + bl, x) - b;
}
// 带边权更新并查集模板
int p[N], d[N];
int find(int x) {
if (x == p[x]) return x;
int root = find(p[x]);
d[x] += d[p[x]];
return p[x] = root;
}
int main() {
scanf("%d %d", &n, &m);
n = 0; // 序列的长度没有用处,我们只关心每个a,b范围内的数字1的个数
for (int i = 1; i < N; i++) p[i] = i; // 初始化并查集
for (int i = 1; i <= m; i++) {
int x, y;
char t[100];
scanf("%d %d %s", &x, &y, t);
g[i].x = x, g[i].y = y;
if (t[0] == 'e')
g[i].e = 0;
else
g[i].e = 1;
// 记录下来
b[bl++] = x, b[bl++] = y;
}
// 离散化去重
sort(b, b + 2 * m);
bl = unique(b, b + 2 * m) - b;
int res = m;
for (int i = 1; i <= m; i++) {
int a = g[i].x, b = g[i].y, e = g[i].e;
// 类前缀和
a = get(a - 1), b = get(b);
int t = 0; // 偶数个1
if (e == 1) t = 1; // 奇数个1
// 并查集
int pa = find(a), pb = find(b);
if (pa == pb) {
if (abs(d[a] - d[b]) % 2 != t) {
res = i - 1; // 最后一条正确的序号
break;
}
} else {
p[pa] = pb;
d[pa] = abs(d[a] - d[b] - t) % 2;
}
}
printf("%d\n", res);
return 0;
}

View File

@@ -0,0 +1,54 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 40010, B = N / 2;
// 简化版本的食物链
int n, m;
// 无序离散化
unordered_map<int, int> S;
int get(int x) {
if (S[x] == 0) S[x] = ++n;
return S[x];
}
// 并查集
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d %d", &n, &m);
n = 0;
for (int i = 1; i < N; i++) p[i] = i;
int res = m;
for (int i = 1; i <= m; i++) {
int a, b;
scanf("%d %d", &a, &b);
char t[100];
scanf("%s", t);
a = get(a - 1), b = get(b); // 计算出新的在并查集中的号
if (t[0] == 'e') { // 偶数个1
if (find(a + B) == find(b)) { // 如果奇偶性不同,因为b与a+B相同
res = i - 1;
break;
}
// join两个奇偶相同的集合
p[find(a)] = find(b);
p[find(a + B)] = find(b + B);
} else { // 奇数个1
if (find(a) == find(b)) {
res = i - 1;
break;
}
// join两个奇偶不相同的集合
p[find(a + B)] = find(b);
p[find(a)] = find(b + B);
}
}
printf("%d\n", res);
return 0;
}

View File

@@ -0,0 +1,74 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 40010, B = N / 2;
// 结构体记录原始输入
struct Node {
int x, y, e;
} g[N];
int n, m;
// 离散化静态数组+二分查找新位置
int b[N], bl;
int get(int x) {
return lower_bound(b, b + bl, x) - b;
}
// 并查集
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d %d", &n, &m);
n = 0;
for (int i = 1; i < N; i++) p[i] = i;
for (int i = 1; i <= m; i++) {
int x, y;
char t[100];
scanf("%d %d %s", &x, &y, t);
g[i].x = x, g[i].y = y;
if (t[0] == 'e')
g[i].e = 0;
else
g[i].e = 1;
// 记录下来
b[bl++] = x, b[bl++] = y;
}
// 离散化去重
sort(b, b + 2 * m);
bl = unique(b, b + 2 * m) - b;
int res = m;
for (int i = 1; i <= m; i++) {
int a = g[i].x, b = g[i].y, e = g[i].e;
a = get(a - 1), b = get(b); // 计算出新的在并查集中的号
if (e == 0) { // 偶数个1
if (find(a + B) == find(b)) { // 如果奇偶性不同,因为b与a+B相同
res = i - 1;
break;
}
// join两个奇偶相同的集合
p[find(a)] = find(b);
p[find(a + B)] = find(b + B);
} else { // 奇数个1
if (find(a) == find(b)) {
res = i - 1;
break;
}
// join两个奇偶不相同的集合
p[find(a + B)] = find(b);
p[find(a)] = find(b + B);
}
}
printf("%d\n", res);
return 0;
}