逆向并查集(zoj 3261,hdoj 4496)

zoj 3261 Connections in Galaxy War

题目大意: 银河中一些星球之间有通道可以联系,然而另一个维度的怪物将某些联系破坏了。因此当有些星球需要帮助时,就需要判断一下能不能找到一个直接或间接联系的星球,并且这个星球的力量是与该星球连接的所有星球中最大的。如果有这么一个星球,则输出该星球的编号,如果有多个星球是最大的,输出编号最小的那个。如果找不到这样的星球,则输出“-1”。
在输入中,首先输入星球个数n,第二行是n个数,表示每个星球的力量。下一行是一个整数m,表示所有的联系个数,下面是m行,每行两个整数,表示哪两个星球有联系。下一行是一个整数q,表示处理个数,有两种情况:
第一种是:destroy a b 表示摧毁a,b之间的联系
第二种是:query a 表示询问a星球能否得到帮助

题解: 我们所熟悉的并查集只有加边的作用,并查集并没有删除边的作用。一开始拿到这个题一脸懵逼 ,无从下手,然后去看dalao代码,发现这是逆向并查集,正向是删边,如果反向来看的话就是加边了啊,这不正是并查集吗?
这里的逆向的意思是,先离线处理所有的操作,然后再逆向处理操作,在读入的时候,所有被删除的边先不要添加到并查集里,逆向处理时,遇到“destroy”时,将这个边加入到并查集中。
dalao的做法是用map存储边与编号的关系,然后再用used数组记录哪条边是要被删除的,很巧妙的将编号与边结合在一起。

AC代码:

#include <iostream>
#include <map>
#include <string.h>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int MAX=10001;
int power[MAX];//每个行星的战斗力
map<int,int>mp[MAX];//存边,以及边的编号
int used[2*MAX];//与map相结合,记录哪条边需要删除
int parent[MAX];
int ans[5*MAX];
int n;
struct edge//记录边,方便通过下标查询连接两个边的节点
{
    int x;
    int y;
}edges[2*MAX];
struct ques//记录问题,如果是destroy ,mark 记为1,如果是query,mark记为0
{
    int mark;
    int x;
    int y;
}que[5*MAX];
struct re//要求的最大力量和最小的编号
{
    int height;
    int num;
}res[MAX];
void init()//初始化
{
    memset(used,0,sizeof(used));
    for(int i=0;i<n;i++)
    {
        parent[i]=i;
        res[i].height=power[i];
        res[i].num=i;
        mp[i].clear();
    }
}
int find_root(int x)
{
    return parent[x]==x ? x : parent[x]=find_root(parent[x]);
}
void unite(int x,int y)
{
    x=find_root(x);
    y=find_root(y);
    if(x!=y)
    {
        parent[x]=y;
        if(res[x].height>res[y].height)
        {
            res[y].height=res[x].height;
            res[y].num=res[x].num;
        }
        else
        if(res[x].height==res[y].height&&res[x].num<res[y].num)
                res[y].num=res[x].num;
    }
}
int main()
{
    int flag=0;
    while(scanf("%d",&n)==1)
    {
        if(!flag)
            flag=1;
        else
            printf("\n");
        for(int i=0;i<n;i++)
            scanf("%d",&power[i]);
        init();
        int m;
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            if(x>y)
                swap(x,y);
            mp[x][y]=i;
            edges[i].x=x;
            edges[i].y=y;
        }
        int q;
        scanf("%d",&q);
        for(int i=0;i<q;i++)
        {
            char query[15];
            scanf("%s",query);
            if(query[0]=='q')
            {
                scanf("%d",&que[i].x);
                que[i].mark=0;
            }
            else
            {
                int x,y;
                scanf("%d%d",&x,&y);
                if(x>y)
                    swap(x,y);
                used[mp[x][y]]=1;//记录被删掉的边
                que[i].x=x;
                que[i].y=y;
                que[i].mark=1;
            }
        }
        for(int i=0;i<m;i++)//将没被删除的边先合并成集合
        {
            if(used[i])
                continue;
            else
                unite(edges[i].x,edges[i].y);
        }
        int t=0;
        for(int i=q-1;i>=0;i--)
        {
            if(!que[i].mark)//询问是否能找到帮助
            {
                int tmp=find_root(que[i].x);
                if(power[que[i].x]<res[tmp].height)
                    ans[t++]=res[tmp].num;
                else
                    ans[t++]=-1;
            }
            else
                unite(que[i].x,que[i].y);//将删除边转化为合并边
        }
        for(int i=t-1;i>=0;i--)
            printf("%d\n",ans[i]);
    }
}

hdoj 4496 D-City

题目大意: 一个怪兽要把整个D城的联系全部摧毁,题目要求输出在摧毁一个联系时还剩多少分块。当且仅当它们直接或间接地彼此连接时,两个点在相同的连接块中。

题解: 同上题一样,离线处理,只不过这题比较简单,因为是“全部摧毁”,所以在最后总是输出节点个数。这题用二维数组存边,开一个 edge[MAX][2] 数组,联系的第一个数存在edge[i][0]里,第二个数存在edge[i][1]里(这种存边方法好巧妙哦~, 原谅一个菜鸟没见过世面 )。

做这题的时候还发现一个有趣的事情:
scanf("%d%d",&n,&m)的返回值为2(如果输入正确的话)
当遇到EOF时,返回值为-1,这时可以用~scanf来判断输入。这里 ~ 是取反符号,-1的补码取反之后为0,可以作为判断循环的条件。
当时将scanf("%d%d",&n,&m)的返回值设为1,WA了N发。。。
具体请见dalao详解(关于scanf的)

AC代码:

#include <iostream>
#include <map>
#include <stdio.h>
using namespace std;
const int MAX=10001;
int parent[MAX];
int edge[10*MAX][2];
int ans[10*MAX];
int n,m;
void init()
{
    for(int i=0;i<=n;i++)
    {
        parent[i]=i;
    }
}
int find_root(int x)
{
    return parent[x]==x ? x : parent[x]=find_root(parent[x]);
}
bool unite(int x,int y)
{
    x=find_root(x);
    y=find_root(y);
    if(x!=y)
    {
        parent[y]=x;
        return true;
    }
    return false;
}
int main()
{
    while(scanf("%d%d",&n,&m)==2)
    {
        int city=n;
        init();
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&edge[i][0],&edge[i][1]);
        }
        for(int i=m-1;i>=0;i--)
        {
            if(unite(edge[i][0],edge[i][1]))
                ans[i]=city--;
            else
                ans[i]=city;
        }
        for(int i=0;i<m;i++)
            printf("%d\n",ans[i]);
    }
}
发布了32 篇原创文章 · 获赞 12 · 访问量 1387

猜你喜欢

转载自blog.csdn.net/qq_18873031/article/details/99678551