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<int, int> 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;
}