种类并查集小结

最近做了两道种类并查集,各种看题解终于弄懂了..

种类并查集就是给定的数据分属不同阵营,根据所给的信息把点连到一个或多个集合,然后通过在集合之间的所属关系,推断某两个元素之间的关系。

在做这类题目时,一般至少需要两个数组,f记录元素的父节点,dep数组记录某个元素到它根节点这一区间的状态。

以POJ 1733题为例

Parity game

Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 6174 Accepted: 2382

Description

Now and then you play the following game with your friend. Your friend writes down a sequence consisting of zeroes and ones. You choose a continuous subsequence (for example the subsequence from the third to the fifth digit inclusively) and ask him, whether this subsequence contains even or odd number of ones. Your friend answers your question and you can ask him about another subsequence and so on. Your task is to guess the entire sequence of numbers.

You suspect some of your friend's answers may not be correct and you want to convict him of falsehood. Thus you have decided to write a program to help you in this matter. The program will receive a series of your questions together with the answers you have received from your friend. The aim of this program is to find the first answer which is provably wrong, i.e. that there exists a sequence satisfying answers to all the previous questions, but no such sequence satisfies this answer.

Input

The first line of input contains one number, which is the length of the sequence of zeroes and ones. This length is less or equal to 1000000000. In the second line, there is one positive integer which is the number of questions asked and answers to them. The number of questions and answers is less or equal to 5000. The remaining lines specify questions and answers. Each line contains one question and the answer to this question: two integers (the position of the first and last digit in the chosen subsequence) and one word which is either `even' or `odd' (the answer, i.e. the parity of the number of ones in the chosen subsequence, where `even' means an even number of ones and `odd' means an odd number).

Output

There is only one line in output containing one integer X. Number X says that there exists a sequence of zeroes and ones satisfying first X parity conditions, but there exists none satisfying X+1 conditions. If there exists a sequence of zeroes and ones satisfying all the given conditions, then number X should be the number of all the questions asked.

Sample Input

10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd

Sample Output

3

 

 

题目大意是有一段序列,给你一个区间内1的个数是奇数还是偶数,通过给你的讯息推断哪条讯息是错的,输出错误讯息标号减一。

 

在这道题目里,可以把每次给定的区间连至一棵树上,f[x]表示X的父节点,dep[x]表示x到它的根节点的奇偶性。

由于奇偶性只有两种状态,那么可以用1代表是奇数,用0代表是偶数。

每次输入两个区间端点的值后,先找到它的根节点,判断是否已被连接,如果是没有,就把他们连接起来,并更新相应的dep值。

eg:输入x,y。找到根节点tx,ty。并且tx!=ty。

那么由于f[ty]改变,就要更新dep[ty]。

这里引入神奇的偏移向量。//膜拜这个思想的发明者...

用tx->ty表示dep[ty]。 那么可以得到 dep[ty]=tx->ty=tx->x+x->y+y->ty=(dep[x]+add-dep[y])%2。

这样就推出了更新dep值的公式。

同理,如果tx,ty相同,那么只需判断[x,y]这段区间的奇偶性和给定的奇偶性是否相同。

x->y=x->tx+tx->y=(dep[y]-dep[x])%2

除此之外,在使用find函数寻找tx,ty时,会用到路径压缩,路径压缩时会变更元素的父节点,所以要更新它的dep值。

ffx->fx+fx->x=ffx->x;  dep[x] = (dep[x] + dep[fa])%2;

 

这道题还有一点是需要离散化。由于数据范围是给到10^9,而最多给出5000个询问,那么10^9个点中最多有10^5个点被提及。

 

附代码


#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
#include <string>
#define N 5007
#define M 15007
using namespace std;

struct node
{
    int x,y;
    char res[10];
}nd[N];

int a[2*N],len,f[2*N],dep[2*N];//离散化存点,长度,存父节点。//dep数组表示x与x的根节点之间的奇偶性

int n;

void init(int m)
{
    for (int i = 0; i <= m; ++i)
    {
        f[i] = i;//初始化每个点的父节点为它本身
        dep[i] = 0;//初始化每个区间的奇偶性为0(0为偶,1为奇);
    }
}
int Search(int l,int r,int val)//二分查找区间起点/终点对应的离散化数组下标
{
    while (l <= r)
    {
        int mid = (l + r)/2;
        if (a[mid] == val) return mid;
        else if (a[mid] > val) r = mid - 1;
        else l = mid + 1;
    }
    return -1;
}
int find(int x)
{
    int fa = 0;
    if (f[x] != x)
    {
        fa = f[x];
        f[x] = find(f[x]); // 寻找根节点
        dep[x] = (dep[x] + dep[fa])%2;//路径压缩,将fx的父亲变成x的父亲。更新dep数组
  //ffx->fx+fx->x=ffx->x;
    }
    return f[x];
}
int cmp(int a,int b)
{
    return a < b;
}
int main()
{
    int i;
    scanf("%d",&n);
    scanf("%d",&n);
    len = 1;
    for (i = 0; i < n; ++i)
    {
        scanf("%d%d%s",&nd[i].x,&nd[i].y,nd[i].res);
        a[len++] = nd[i].x; a[len++] = nd[i].y;
    }


    sort(a + 1,a + len,cmp);//这里的长度是len-1


    int tn = 1;
    for (i = 2; i < len; ++i) if (a[i] != a[i - 1]) a[++tn] = a[i];//去重,离散化


    init(tn);
    int ans = 0;
    for (i = 0; i < n; ++i)
    {
        int x = Search(1,tn,nd[i].x);
        int y = Search(1,tn,nd[i].y);

        x--;   //左端点减一,使区间合并
        int tx = find(x);//寻找根节点
        int ty = find(y);
        int add = 0;
        if (nd[i].res[0] == 'e') add = 0;//为偶数加0,为奇数加1
        else add = 1;
        if (tx != ty)//若不属于同一集合
        {
            f[ty] = tx; //相连至同一集合
            dep[ty] = (dep[x] + add - dep[y])%2; //此集合的奇偶性更新
   //偏移向量:x->y 偏移量为0 表示x到y的奇偶性为偶。x->y 偏移量为1 表示x到y的奇偶性为奇。
            //连接ty到tx时,dep[ty]=tx->ty=tx->x+x->y+y->ty=(dep[x]+add-dep[y])%2
        }
        else
        {
            if ((abs(dep[y] - dep[x]))%2 != add) break;//已经存下的奇偶性与讯息的奇偶性不同,则矛盾,为假话
   //验证x,y间的偏移向量与add是否一致。
   //x->y=x->tx+tx->y=(dep[y]-dep[x])%2
        }
        ans++;  //讯息标号
    }
    printf("%d\n",ans);

    return 0;
}
//若l到r为奇数:0到l为偶数,那么0到r为偶数。0到l为奇数,那么0到r为偶数
//若l到r为偶数:0到l为偶数,那么0到r为偶数。0到l为奇数,那么0到r为奇数

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值