题目描述

思路
怎么得到这个序列中每一段的关系?
- 我们可以把这个只包含0和1的序列看作一个数组,0表示当前位置为0,1表示当前位置为1,利用前缀和的性质可以知道某一段中所包含的1的数量
- sum1 = a[r] - a[l-1]
- 如果sum1为偶数,那么a[r] 和 a[l-1]的奇偶性相同
- 如果sum1为奇数,那么a[r] 和 a[l-1]的奇偶性不同
- 找到它们之间的关系,我们就可以使用并查集来存储他们
为什么要离散化?
- 序列的长度小于等于1e9,然而序列中下标出现的次数最多为1e4,所以使用离散化
种类并查集
- 序列中某一段有两种关系,奇数个1、偶数个1
- 我们定义两个扩展域,1~n表示偶数,n + 1 ~ 2 * n表示奇数
- 每次知道一个关系之后,将偶数区域和奇数区域都进行处理,像枚举一样,不漏掉每一种情况
- 并查集中存储的是区域与区域之间的关系,如果有一个关系成立,那么这个集合中其它的关系都成立
代码实现
#include <iostream>
#include <algorithm>
#include <vector>using namespace std;const int N = 1e4 + 10;int fa[2 * N]; // 两个扩展域:1~N表示偶数关系,N+1~2N表示奇数关系
int n, m;
vector<int> ans; // 离散化数组struct node // 结构体,存储每一段区间的左右端点
{int x, y;string op;
}a[N];int get(int u) // 二分查找,将原数据离散化成下标
{int l = 0, r = ans.size();while(l + 1 != r){int mid = l + r >> 1;if(ans[mid] < u) l = mid;else r = mid;}return r;
}int find(int u) // 返回当前元素的祖宗元素,在哪个集合中
{if(fa[u] != u) fa[u] = find(fa[u]);return fa[u];
}void merge(int x, int y) // 将x合并到y所在的集合中
{fa[find(x)] = find(y);
}int main()
{cin >> n >> m;for(int i = 1; i <= m; i++){cin >> a[i].x >> a[i].y >> a[i].op; a[i].x--; // 因为利用了前缀和的性质,所以要将左端点-1,满足sum = a[r] - a[l-1]ans.push_back(a[i].x);ans.push_back(a[i].y);}sort(ans.begin(), ans.end()); // 将数据进行排序ans.erase(unique(ans.begin(), ans.end()), ans.end()); // 进行去重ans.insert(ans.begin(), 0); // 将离散化之后的下标从1开始for(int i = 1;i <= m; i++) // 找到每个区间左右端点离散化之后的数据,使用新数据{a[i].x = get(a[i].x);a[i].y = get(a[i].y);}for(int i = 0;i < 2 * N; i++) fa[i] = i; // 初始化集合,每一个元素在不同的集合中n = ans.size(); // 每个扩展域的范围for(int i = 1; i <= m; i++) // 开始遍历每一个回答,判断左右端点集合中的关系{int x = a[i].x, y = a[i].y;string op = a[i].op;if(op == "even") // 有偶数个1{// 只需要判断一种情况就可,因为他们的关系是对称的if(find(x) == find(y + n)) // x为偶数的集合中,存在y为奇数的关系,矛盾{cout << i - 1 << endl;return 0;}// x与y的奇偶性相同merge(x, y); // 将x为偶数,y为偶数放在一个集合中merge(x + n, y + n); // 将x为奇数,y为奇数放在一个集合中}else // 有奇数个1,那么x和y的奇偶性不同{if(find(x) == find(y)) // x为偶数的集合中,存在y为偶数的关系,矛盾{cout << i - 1 << endl;return 0;}merge(x, y + n); // 将x为偶数,y为奇数放在一个集合中merge(x + n, y); // 将x为奇数,y为偶数放在一个集合中}}cout << m << endl;return 0;
}
权值并查集
- 每一个区间的奇偶性有两种情况,奇数或者偶数
- 我们可以维护集合中每个区间的关系,这个关系可以用集合中的子区间到祖宗区间的距离来表示,因为有两种情况,所以当距离d[i] % 2 == 0时,当前区间的奇偶性和祖宗元素的奇偶性相同,d[i] % 2 ==1时,当前区间的奇偶性和祖宗元素的奇偶性不同
- 每一个集合中的区间相对于祖宗区间的奇偶性是已知的,所以集合中所有区间的奇偶性关系都是已知的
代码实现
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;const int N = 1e5 + 10;int fa[N]; // 初始化每一个集合都是独立的
int d[N]; // 集合中子区间到祖宗区间的距离,可以判断奇偶关系
vector<int> ans; // 离散化数组
int n, m;struct node // 存储每一个回答
{int x, y;string op;
}a[N];int get(int u) // 二分查找进行离散化
{int l = 0, r = ans.size();while(l + 1 != r){int mid = l + r >> 1;if(ans[mid] < u) l = mid;else r = mid;}return r;
}int find(int u) // 找到当前区间的祖宗区间,并进行路径压缩和更新到祖宗区间的距离
{if(fa[u] != u){int t = find(fa[u]);d[u] += d[fa[u]];fa[u] = t;}return fa[u];
}int main()
{cin >> n >> m;for(int i = 1;i <= m; i++){cin >> a[i].x >> a[i].y >> a[i].op;a[i].x--;ans.push_back(a[i].x);ans.push_back(a[i].y);}sort(ans.begin(), ans.end());ans.erase(unique(ans.begin(), ans.end()), ans.end());ans.insert(ans.begin(), 0);for(int i = 1; i <= m; i++) {a[i].x = get(a[i].x);a[i].y = get(a[i].y);}for(int i = 1; i <= ans.size(); i++) fa[i] = i;for(int i = 1;i <= m; i++){int fx = find(a[i].x), fy = find(a[i].y);string op = a[i].op;if(op == "even"){if(fx != fy){d[fx] = d[a[i].x] ^ d[a[i].y];fa[fx] = fy;}else {if((d[a[i].x] + d[a[i].y]) % 2 != 0){cout << i - 1 << endl;return 0;}}}else{if(fx != fy){d[fx] = d[a[i].x] ^ d[a[i].y] ^ 1;fa[fx] = fy;}else{if((d[a[i].x] + d[a[i].y]) % 2 == 0){cout << i - 1 << endl;return 0;}}}}cout << m << endl;return 0;
}