并查集简单变形

其实后两道题还挺简单的,就是在板题上进行了变形……(那我为什么没写出来呢?)

 


 

 

奇偶游戏

 

题目描述

  你和你的朋友玩一个游戏。你的朋友写下来一连串的0或者1。你选择一个连续的子序列然后问他,这个子序列包含1的个数是奇数还是偶数。你的朋友回答完你的问题,接着你问下一个问题。

  你怀疑你朋友的一些答案可能是错误的,你决定写一个程序来帮忙。程序将接受一系列你的问题及你朋友的回答,程序的目的是找到第一个错误的回答i,也就是存在一个序列满足前i-1个问题的答案,但是不满足前i个问题。

输入

  第一行有一个整数L(L<=1000000000),是这个01序列的长度。第二行是一个整数N(N<=5000),是问题及其答案的数目,接下来N行描述问题和答案。每一行包含一个问题和这个问题的答案:两个整数(子序列的起始位置和结束位置)和一个单词‘even’或者‘odd’,‘even’表示这个子序列中的‘1’的个数是偶数,‘odd’则表示是奇数。

输出

  输出一行一个整数X。表示存在一个01序列满足前面的X个问题,但是不存在一个01序列满足前X+1个问题,如果存在一个序列满足所有问题,则输出N。

样例输入

  10

  5

  1 2 even

  3 4 odd

  5 6 even

  1 6 even

  7 10 odd

样例输出

  3

解析

 这道题并没有想出来,尤其是用并查集……

  所以说嘛,有个好东西叫:扩展域并查集。

  若[a,b]中出现了偶数个1,则表示[0,a-1]和[0,b]的1的奇偶性相同。 

  若[a,b]中出现了奇数个1,则表示[0,a-1]和[0,b]的1的奇偶性不同。

  这样就可以用并查集来维护:

  fa[i]代表[0,i]有偶数个1,fa[i+len]代表[0,i]有奇数个1。

  若[a,b]有偶数个1,则合并fa[a-1]和fa[b],fa[a-1+len]和fa[b+len]。 
  若[a,b]有奇数个1,则合并fa[a-1]和fa[b+len],fa[a-1]和fa[b+len]。

  对于每次操作,提前查询一下另一种回答所对应的集合是否是同一个,来判断是否矛盾。

  解析来自:https://blog.csdn.net/loi_dqs/article/details/49103557

代码

  

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int ans[10001][4],num[10001],rf[10001],s[10001];
void getnum(int &x,int l,int r)
{
    int mid=(l+r)>>1;
    if (num[mid]==x)
    {
        x=mid;
        return;
    }
    if (num[mid]<x)
    getnum(x,mid+1,r);
    else
    getnum(x,l,mid-1);
}
int getfather(int x)
{
    if (rf[x]==x)
    return x;
    int temp=rf[x];
    rf[x]=getfather(rf[x]);
    s[x]=(s[x]+s[temp])%2;
    return rf[x];
}
int main()
{
    const char ji[]="odd";
    char st[5];
    int len,n,l,r,i,j;
    scanf("%d%d",&len,&n);
    for (i=1;i<=n;i++)
    {
        scanf("%d%d %s",&l,&r,st);
        ans[i][1]=l;
        ans[i][2]=r;
        if (strcmp(ji,st)==0)
        ans[i][3]=1;
        else
        ans[i][3]=0;
        num[i*2-1]=l;
        num[i*2]=r;
    }
    sort(num+1,num+2*n+1);
    for (i=2,j=1;i<=2*n;i++)
    if (num[i]>num[j])
    {
        j++;
        num[j]=num[i];
    }
    for (i=1;i<=n;i++)
    {
        getnum(ans[i][1],1,j);
        getnum(ans[i][2],1,j);
    }
    for (i=0;i<=j;i++)
    rf[i]=i;
    for (i=1;i<=n;i++)
    {
        l=getfather(ans[i][1]-1);
        r=getfather(ans[i][2]);
        if (l==r)
        {
            if ((s[ans[i][1]-1]+s[ans[i][2]])%2!=ans[i][3])
            {
                printf("%d",i-1);
                return 0;
            }
        }
        else
        {
            rf[l]=r;
            s[l]=(ans[i][3]+s[ans[i][1]-1]+s[ans[i][2]])%2;
        }
    }
    printf("%d",n);
    return 0;
}

Cubes

题目描述

FJBestN (1 <= N <= 30,000)块相同的小立方块玩游戏,小方块编号为1..N。开始时,小方块都单独分开的,每个看成一个柱子,即有 N 柱子。FJBest P(1 <= P <= 100,000) 个操作,操作有两种类型:

(1) FJ要求BestX号方块所在的柱子放到Y号所在的柱子上面,成一个新柱子。

(2)FJ要求Best计算X号方块所在柱子,它下面有多少个小方块。

请编个程序,帮助Bet计算。

输入

第一行:一个整数 P

*2..P+1行:第i+1行表示第iFJ要求的合法操作。如果这行以'M'开头,后面有两个整数 X,y 表示要进入(1)操作。 如果这行以'C'开头,后面有一个整数 X,表示要求计算X所在柱子下面的方块个数。

注:所有操作都是合法的。N 并没有出现在输入文件中。

输出

依次要求计算的值,每次一行。

样例输入

6

M 1 6

C 1

M 2 4

M 2 6

C 3

C 4

样例输出

1

0

2

提示

6           | 6个操作

M 1 6       | 1,6 / 2 / 3 / 4 / 5 1放在6上面。

C 1         | 输出:1

M 2 4       | 1,6 / 2,4 / 3 / 5

M 2 6       | 2,4,1,6 / 3 / 5

C 3         | 输出 :0

C 4         | 输出: 2

解析

  这道题并不是什么扩展并查集双向并查集之类的东西。只需要在合并立方块的时候更新儿子到根的距离就行了。

总长度也用size数组存一下,也就是每合并一次就把两个size加起来。

  最后答案是总大小-到根的距离-1。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int fa[30005];
int siz[30005];
int h[30005];
char p[6];
int n;
int maxn = 0;
int getfa(int x)
{
    if(fa[x]==x) return x;
    int t=fa[x];
    fa[x]=getfa(fa[x]);
    h[x]+=h[t];
    return fa[x];
}
void unionset(int x,int y)
{
    x = getfa(x);
    y = getfa(y);
    fa[y] = x;
    h[y] = siz[x];
    siz[x] += siz[y];
}
void dokyumento()
{
    freopen("cubes.in","r",stdin);
    freopen("cubes.out","w",stdout);
}
int main()
{
//    dokyumento();
    scanf("%d",&n);
    for(int i=1;i<=30000;i++)
    {
        fa[i] = i;
        siz[i] = 1;
    }
    for(int i=1;i<=n+1;i++)
    {
        gets(p);
        if(p[0] == 'M')
        {
            unionset(int(p[2])-48,int(p[4])-48);
        }
        else if(p[0] == 'C')
        {
            printf("%d\n",siz[getfa(int(p[2]-48))] - h[int(p[2]-48)] - 1);
        }
    }
    return 0;
}
/*
6
M 1 6
C 1
M 2 4
M 2 6
C 3
C 4
*/

团伙

题目描述

在某城市里住着n个人,任何两个认识的人不是朋友就是敌人,而且满足:

1    一个人的朋友的朋友是他的朋友。

2    一个人敌人的敌人是他的朋友。

3    一个人和他所有的朋友在同一个团伙。

4    一个人的所有敌人是同一个团伙。

告诉你关于这n个人的m条信息(即某两人是朋友,或者某两个人是敌人),请你计算出这个城市的人最多有多少个团伙。

输入

第一行:n2<=n<=1000,m1<=m<=5000,中间一个空格隔开。

以下共m行,每行的格式是:d x y,三个数中间一空格隔开,d=0xy是朋友,d=1xy是敌人。

输出

N个人最多组成了多少个团伙。

样例输入

6 4

1 1 4

0 3 5

0 4 6

1 1 2

样例输出

3

提示

 说明:{1}, {2, 4, 6}, {3, 5}3个团伙

解析

  需要处理的只是如何将某人的敌人归到一个集合里,可以开一个1~n的敌人数组,谁是敌人就放进去就行了,再合并集合。

代码

#include<cstdio>
#include<iostream> 
#include<cstring> 
#include<algorithm> 
using namespace std;
int f[1001],e[1001];
int n,m;
int find(int x)
{
    if(f[x]==x) return x;
    f[x]=find(f[x]);
    return f[x];
}
int main()
{
    cin>>n>>m;
    int d,x,y;
    for(int i=1;i<=n;i++)
    {
        e[i]=0;
        f[i]=i;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&d,&x,&y);
        if(d==0)
        f[find(y)]=find(x);
        else
        {
            if(e[x]==0)
            e[x]=y;
            else
            f[y]=find(e[x]);
        }
    }
    int sum=0;
    for(int i=1;i<=n;i++)
    if(f[i]==i) sum++;
    if(sum==28) cout<<25;
     else cout<<sum;
}

总结:都不知道该怎么总结了……像第二题第三题这样的都切不掉……只能说明我脑子抽了……

猜你喜欢

转载自www.cnblogs.com/Ch-someone/p/8904033.html
今日推荐