POJ1733 Parity game (离散化+带权并查集)

版权声明:《本文为博主原创文章,转载请注明出处。 》 https://blog.csdn.net/zbq_tt5/article/details/86559712

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

本题中,长度小于等于1e9,而问题数相比较长度来说却很小。

这个时候为了提高效率,就要采用一些优化的方法来解决问题。

离散化

就是把一些很离散的点给重新分配

举个例子,如果一个坐标轴很长(>1e10),给你1e4个坐标,询问某一个点,坐标比它小的点有多少。

很容易就知道,对于1e4个点,我们不必把他们在坐标轴上的位置都表示出来,因为我们比较有多少比它小的话,只需要知道他们之间的相对大小就可以,而不是绝对大小,这,就需要离散化。

下面列举一种离散化:

其实就是用一个辅助的数组把你要离散的所有数据存下来

然后排序,排序是为了后面的二分

去重,因为我们要保证相同的元素离散化后数字相同。

再用二分把离散化后的数字放回原数组

可以说,这里是环环相扣的!

#include<algorithm>
#include<stdio.h>
#include<iostream>
using namespace std;
const int MAXN=50005;
int al[MAXN],cnt,a[MAXN],n;
int main()
{
    scanf("%d",&n);
    for(int i=0; i<n; i++)
    {
        scanf("%d",&a[i]);
        al[i]=a[i];
    }
    sort(al,al+n);
    cnt = unique(al, al+n)-al;
}

这里的cnt代表的就是去重后的个数。

二分

在本题中,cnt就可以说是所有出现过的输入的数据的个数,那么离散化就是把问题从n压缩到cnt,在cnt这个范围内进行查找的话,效率提高了很多,这个时候再结合二分法,效率又会提高不少。

int erfen(int x)//现在的x代表所要找的
{
    int left=0,right=n;

    while(left<=right)
    {
        int mid=(left+right)>>1;
        if(num[mid]==x)
            return mid;
        else if(num[mid]>x)
            right=mid-1;
        else
            left=mid+1;
    }
    if(num[left]==x)
        return left;
    else
        return right;
}

其中,x代表的是输入的数据,输出的是这个数据对应的一个下标。

接下来的问题就是带权并查集了。

带权并查集

一般的并查集主要记录节点之间的链接关系,而没有其他的具体的信息,仅仅代表某个节点与其父节点之间存在联系,它多用来判断图的连通性,如下图所示,这是一个并查集,其中箭头表示父子关系,可以看到这些边没有记录其他的任何信息。

有的时候在这些边中添加一些额外的信息可以更好的处理需要解决的问题,在每条边中记录额外的信息的并查集就是带权并查集

首先,是递归版的压缩路径

int find(int x)
{
    if(x!=father[x])
        father[x]=find(father[x]);
    return father[x];
}

上图就是上一段代码操作的意义,直接让C链接到A不是更好吗,这样就可以省去中间的操作,如果C跟A直接相隔很多节点,这个优化就极大地提升了查找的效率!

与一般的并查集相比,带权并查集只是在find(parent[x])前边加了一步赋值操作,将在查找过程中遇到的所有的节点的父节点都设为最终得到的那个节点。

也就是:

int find(int x)
{
    if(x!=father[x])
    {
        int temp=father[x];
        father[x]=find(father[x]);
        sum[x]+=sum[temp];
    }
    return father[x];
}

相较于上面的代码,这一段多了两行,分别为int temp=father[x];(记录下原本父节点的编号)和sum[x]+=sum[temp];(将当前节点的权值加上原本父节点的权值,此时父节点的权值已经是父节点到根节点的权值了,因此加上这个权值就会得到当前节点到根节点的权值)。

合并:如果说两个值的根不同,就进行合并,并且要对合并的权值进行赋值!

通过这个图举个例子:

我们一般在进行找祖先的操作的时候有这样两句

int fx=find[x];
int fy=find[y];

这两句就可以看作是x到px,y到py这两部操作,举个例子,如果说现在是古时候,人们在江湖上面行走,见到跟自己不同门派的人就要打一架,可是大家都提倡和平共处,所以

对于本题来说,我们考虑的只有两种情况,也就是奇数或者偶数,那么我们在赋值的时候只考虑0 、1就行了,这样我们通过对二进行取余的操作就可以实现最后想要的结果。

因为奇数加奇数等于偶数,偶数加偶数等于偶数,奇数加偶数等于奇数.......

如果说0代表偶数,1代表奇数的话

(1+1)%2=0,(0+0)%2=0,(0+1)%2=1;

#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<iostream>
using namespace std;
const int maxn=500005;
int n,m;
int num[maxn],father[maxn],Rank[maxn];
char judge[10];
int find(int x)
{
    if(x!=father[x])
    {
        int t=father[x];
        father[x]=find(father[x]);
        Rank[x]=(Rank[x]+Rank[t])%2;
        return father[x];
    }
    return x;
}
struct xiao
{
    int x,y,jiou;

} a[maxn];

int erfen(int x)//现在的x代表所要找的
{
    int left=0,right=n;
    while(left+1<right)
    {
        int mid=(left+right)/2;
        if(num[mid]==x)
            return mid;
        else if(num[mid]>x)
            right=mid-1;
        else
            left=mid+1;
    }
    if(num[left]==x)
        return left;
    else
        return right;
}
int main()
{
        while(~scanf("%d",&n))
        {
        scanf("%d",&m);
        int num_puts=0;
        int cnt=0;
        for(int i=0; i<m; ++i)
        {
            scanf("%d%d%s",&a[i].x,&a[i].y,judge);
            a[i].x--;
            if(judge[0]=='e')
                a[i].jiou=0;
            else
                a[i].jiou=1;
            num[cnt++]=a[i].x;
            num[cnt++]=a[i].y;
        }
        for(int i=0; i<=cnt; ++i)
        {
            father[i]=i;
            Rank[i]=0;
        }
        sort(num,num+cnt);
        n=unique(num,num+cnt)-num;
        int i;
        for(i=0; i<m; ++i)
        {
            int fx=erfen(a[i].x);
            int fy=erfen(a[i].y);
            int x=find(fx);
            int y=find(fy);
            if(x!=y)
            {
                father[y]=x;
                Rank[y]=(Rank[fx]-Rank[fy]+2+a[i].jiou)%2;
            }
            else
            {
                if((Rank[fx]+Rank[fy]+2)%2!=a[i].jiou)
                {printf("%d\n",i);
                 break;
                }
            }
        }
        if(i==m)
            printf("%d\n",m);
        }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zbq_tt5/article/details/86559712
今日推荐