Parity game POJ - 1733
题意:
有一个长度已知的01串,给出一系列包含\(l,r\)的语句,表示\([l,r]\)这个区间中的1是奇数个还是偶数个。问前多少条语句是正确的。
思路:
一:集合元素的选定
首先为了能连通,需要把所给闭区间改为左开右闭区间,如第2~4位上有偶数个1,改写为\((1,4]\)上有偶数个1。那么每一个左端点都可视为集合元素,其父节点为右端点。每个结点\(i\)与其根节点\(root[i]\)的关系\(rela[i]\),即区间\((i,root[i])\)上1的个数的奇偶性。
对于给出的闭区间\(u - v\),结点编号应该是\(u-1\)和\(v\)。
注意要先\(-1\)之后再离散化。
const int maxn=1e4+10;
int fa[maxn];
int rela[maxn];
//0表示偶数个,1表示奇数个
二:离散化
由于结点编号可能大至\(1e9\),如果不做处理,将编号直接作为数组下标显然不可行(如果一个结点的编号是\(1e9\),意味着数组大小要开到\(1e9\)才能存这个结点)。又因为\(n<=5000\),也即结点数最多只有\(10000\)个,可以将所给编号离散化至\(10000\)以内的数。
//用map将原编号离散化,并将返回新编号
int Hash(int x){
if(mp.find(x)==mp.end()) mp[x]=++pos;
return mp[x];
}
三:压缩路径时的关系维护
若结点\(B\)是结点\(A\)的父节点,已知:\(A\)与\(B\)的关系\(rela[A]\)、\(B\)与根节点的关系\(rela[B]\),求\(A\)与根节点的关系。
这里的关系即两个结点之间1的个数的奇偶性,因此所有情况如下表所示:
rela[A] | rela[B] | A与根节点的关系 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
可以看出,这是异或运算。
int find(int A){
if(fa[A] == -1) return A;
//无父节点,说明A已是最右边的端点
int root_B = find(fa[A]);
//B即fa[A]
rela[A] = rela[A] ^ rela[fa[A]];
//更新结点A与根节点的关系
return fa[A] = root_B;
//更新关系后,再将A与根节点连接
}
四:合并集合时的关系维护
已知:区间\((u,fa[u] ]\)上的关系\(rela[u]\)、区间\((v,fa[v]]\)上的关系\(rela[v]\),现又知道区间\((u,v]\)上的关系\(r\),如何合并集合?
(务必注意这里的\(fa[u]\)和\(fa[v]\)都是路径压缩后更新过的,是根节点而不是父节点。)
合并集合,实际上是需要求两个根节点\(fa[u]\)与\(fa[v]\)之间的关系\(rela[fa[u]]\)。
举个例子:已知区间\((0,3]\)为\(1\),区间\((6,8]\)为\(0\),现又已知区间\((0,6]\)为\(1\),求区间\((3,8]\)。结果是0。
所有情况如下表所示:
rela[u] | rela[v] | r | fa[u]与fa[v]的关系 |
---|---|---|---|
0 | 0 | 1 | 1 |
0 | 1 | 0 | 1 |
0 | 1 | 1 | 0 |
1 | 0 | 0 | 1 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 0 |
1 | 1 | 1 | 1 |
由此可见,\(fa[u]\)与\(fa[v]\)之间的关系\(rela[fa[u]]=rela[u]\) ^ \(rela[v]\) ^ \(r\)
int r1 = find(u);
int r2 = find(v);
//路径压缩,更新u,v根节点
if (r1 != r2) {
//若u,v不在同一集合中
fa[r1] = r2;
rela[r1] = rela[u] ^ rela[v] ^ r;
}
五:关系查询
这一步是判断给出的关系是否符合由先前条件所推出的关系。只有当两结点在同一集合中时,才能确保两结点间的关系可以推出。
判断是否在同一集合中,即判断两结点的根节点是否相同。
if (r1 == r2){
int relation = rela[u] ^ rela[v];
//先推出u,v的关系
return relation == r;
//r为给出的关系
}
完整代码:
(这里是先把所有给出的关系先存起来离线处理,如果在线处理的话代码会更简洁一些)
const int maxn = 1e4+10;
int fa[maxn],rela[maxn];
int x[maxn],y[maxn],r[maxn];
int k, n, pos;
map mp;
//编号离散化
int Hash(int x){
if(mp.find(x)==mp.end()) mp[x]=++pos;
return mp[x];
}
//路径压缩
int find(int x) {
if (fa[x] == -1) return x;//没有右端点了
int tmp = find(fa[x]);
//先找到根
rela[x] = rela[fa[x]] ^ rela[x];
return fa[x] = tmp;
//!注意:维护关系之后再更新x的根节点
}
//集合合并+查询
bool merge(int u,int v,int rr){
int r1 = find(u);
int r2 = find(v);
if(r1 != r2){
fa[r1] = r2;
rela[r1] = rela[u] ^ rela[v] ^ rr;
return true;
//不在同一集合,关系无法由已知推出,那么给出的关系一定是正确的
}
else{
int relation = rela[u] ^ rela[v];
return relation == rr;
}
}
int main()
{
//ios::sync_with_stdio(false);
memset(fa, -1, sizeof(fa));
memset(rela, 0, sizeof(rela));
//初始化为0!不能是-1!
int pos = 0;
int ans = 0;
char str[20];
cin >> k >> n;
for (int i = 1; i <= n; i++) {
int tx, ty;
cin >> tx >> ty;
cin >> str;
x[i] = Hash(tx - 1);
//先取左端点的开区间再离散化
y[i] = Hash(ty);
if (str[0] == 'e') r[i] = 0;
else r[i] = 1;
}
for (int i = 1; i <= n; i++) {
int a = x[i], b = y[i];
if (a > b) swap(a, b);
if (merge(a,b,r[i])) ans++;
//关系正确,答案+1
else break;
//关系错误,直接跳出
}
cout << ans << endl;
return 0;
}