uestc oj 1223 Islands

Islands

这一题利用逆向思维+并查集  水位是连续的自然数

显然题目是要求在不同水位情况下的不同子集个数 显然在水位降低的时候子集的个数是有上一个水位的情况
改变而来,所以可以利用上一次的数据
为简化计算从而使每个节点只计算一次,可以首先进行排序。
先建立结构体存放节点数据  行r 列 c 以及 高度 h 读入数据的同时进行初始化
每个节点的父亲初始化为自身 
按照高度h对节点进行排序   
然后   按照水位逆序查询,首先求ans[i](i为所求最高水位)的子集个数
过程如下  从islans最高楼房高度开始  每次读入一个高度高于 ans[i]的楼房就ans[i]++
后面对楼房的四邻进行查询,如果高度仍高于ans[i] 且与其不是同一子集就ans[i]--  并且进行合并
这样的话  就避免了重复比如一个子集有3个相邻(111模式)的楼房构成  然后每次读入都加了1 共计3 
然后在读入左边1的时候(查询四周)中间的1并入子集 并ans[i]--  再读入中间1的时候由于与左边的1已经属于同一集合
所以不用减1 而右面的1并入集合 ans[i]--   然后再读入右边1的时候不会出现--情况因为三者都是同一集合了
最后得出正确答案  1个集合 
其余情况 同理可得 每次低水位ans[--i]利用高水位ans[i]时候的数据再继续读入楼房高度(高于低水位的数据不用再次读入)因为再次读入结果仍会是ans[i];
最后得出结论;
说的不是很明白 ,较难表达 望见谅!

时间复杂度:集合的合并算法很简单,只要将两棵树的根结点相连即可,这步操作只要O(1)时间复杂度
采用路径压缩之后,查找的时间复杂度也降低为了O(1) 这个题目还进行了排序  额O(n^2)?
那么总体就是O(n^2+1)
空间复杂度  O(n)


#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAX 1000010
using namespace std;  //只有在添加了名字空间之后才能真正调用 sort函数
int d[4][2]={0,1,0,-1,-1,0,1,0};  //  按照上下左右的方向进行搜素  以判别出是否属于同一集合
int T,n,m,i,j,k,s;
struct node
{
    int r,c;
    int h;
}num[MAX];
int father[MAX],h[MAX],ans[MAX],que[MAX];


bool cmp(node a,node b) 
{
   return a.h<b.h;
}


int findFather(int id)
{
    if(id!=father[id]) father[id] = findFather(father[id]);
    return father[id];
}


void unio(int fa,int fb)
{
    father[fb] = fa;
}


bool isInMap(int r,int c)
{
    if(r>0&&c>0&&r<=n&&c<=m) return true;
    else
    return false;
}


void deal()
{
    for(i=n*m,j=k;j>=1;j--)//i代表总结点个数(已经排序)
    {
        ans[j]=ans[j+1];    //j代表水位
        for(;que[j]<num[i].h;i--)
        {
            ans[j]++;
            int t = (num[i].r-1)*m+num[i].c;
            int fa = findFather(t);
           // printf("%d\n",fa);
            for(s=0;s<4;s++)
            {
                int tempr = num[i].r + d[s][1];
                int tempc = num[i].c + d[s][0];
                if(isInMap(tempr,tempc))
                {
                    int id = (tempr-1)*m+tempc;
                    int fb = findFather(id);


                    if(fa == fb||h[id]<=que[j]) continue;
                    else
                    {
                        unio(fa,fb);
                        ans[j]--;
                    }
                }


            }
        }
    }
    for(i=1;i<=k;i++)
          printf("%d ",ans[i]);
    printf("\n");
}


int main()
{
   // freopen("1.txt","r",stdin);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
           for(j=1;j<=m;j++)
         {
             int t = (i-1)*m+j;
             //printf("%d",t);
             scanf("%d",&h[t]);
             num[t].r = i;
             num[t].c = j;
             num[t].h = h[t];
             father[t] = t;
          }


        sort(num+1,num+n*m+1,cmp);
        scanf("%d",&k);
        for(i=1;i<=k;i++)
           {
                scanf("%d",&que[i]);
           }


        memset(ans,0,sizeof(ans));
        deal();
    }
    return 0;
}

其实并查集的那个findFather操作就是一个递归的很简单的,不用想的太多的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值