【带权并查集+离散化】Parity game POJ - 1733

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;
}
posted @ 2020-08-09 22:54  StreamAzure  阅读(133)  评论(0编辑  收藏  举报