[UVA1665]ISLANDS——[并查集]

【原题】
Deep in the Carribean, there is an island even stranger than the Monkey Island, dwelled by Horatio
Torquemada Marley. Not only it has a rectangular shape, but is also divided into an n × m grid. Each
grid field has a certain height. Unfortunately, the sea level started to raise and in year i, the level is
i meters. Another strange feature of the island is that it is made of sponge, and the water can freely
flow through it. Thus, a grid field whose height is at most the current sea level is considered flooded.
Adjacent unflooded fields (i.e., sharing common edge) create unflooded areas. Sailors are interested in
the number of unflooded areas in a given year.
An example of a 4 × 5 island is given below. Numbers denote the heights of respective fields in
meters. Unflooded fields are darker; there are two unflooded areas in the first year and three areas in
the second year.

【题目翻译】
有一块矩形的海域,海域每个点的高度都已知。海面每年都会上涨,第 i i 年的海面高度是i,因此裸露的地方每年都会变化,一个连通块定义为上下左右四个方向联通的裸露区域,有q个询问:这一年的连通块个数是多少个。

【输入格式】
t(1t20) t ( 1 ≤ t ≤ 20 ) 组数据组成,对于每组数据,首先是 nm(1n,m1000) n 、 m ( 1 ≤ n , m ≤ 1000 ) ,表示矩阵大小,然后读入这个矩阵,之后是一行, q(1q10000) q ( 1 ≤ q ≤ 10000 ) ,表示有几个询问。接下来一行有 q q 个数,表示询问的年份。
注意:这里给出的年份保证是递增的。

【输出格式】
t行,每行对应给出那组数据的 q q 个询问的答案。

SampleInput

1
4 5
1 2 3 3 1
1 3 2 2 1
2 1 3 4 3
1 2 2 2 2
5
1 2 3 4 5

SampleOutput S a m p l e O u t p u t

2 3 1 0 0

【题意分析】
蒟蒻最近在练爆搜,读完题目就直接上了个爆搜qwq
结果呢。。。TLE

然后奆佬cy告诉我:

爆搜妥妥tle啊,得用并查集

他的思路:(https://blog.csdn.net/modestcoder_/article/details/81945452)

恍然大悟,mmp爆搜 O(t(qNM)) O ( t ∗ ( q N M ) )
求连通块的常用算法是并查集。

因为给你的年份是单调递增的,因此想到离线处理。
给你的是一个矩阵,我们把这个矩阵拍扁(替罪羊树*1),按照高度降序排一下(保留原坐标),然后在将排序后的序列映射到矩阵里,考虑并查集维护:
为什么要降序?因为深度浅的时候连通块肯定是从深度深的扩展而来
//cy大佬讲得很清楚了

初始化每个格子的父亲都是他自己, O(NM) O ( N M ) 扫一遍矩阵,用一个指针 indicater i n d i c a t e r 记录当前符合要求的是哪一年( indicater i n d i c a t e r 初始化为 q q ,因为year[q]最大),如果当前这个玩意儿比当前高度要低了,那么后面就不可能了,所以 indicater1 i n d i c a t e r − 1

随后对当前节点的四周进行检查:如果没有越界而且高度也是符合要求的,那么用并查集合并,如果发现祖先不一样,那么连通块就要-1个(因为合并了)。

注意:指针如果到0了,那么就说明后面的根本不需要再看下去了,之后的连通块肯定为0个(水漫金山ing)
反之遍历完整个矩阵指针还没到0,就说明高度太大了,后面的年份的连通块个数直接赋为当前值。

Code:

const MAXN = 1500;
direction : array [1..4,1..2] of longint = ((-1,0),(0,-1),(0,1),(1,0));

var t,ii,i,j,xx,yy,cnt,q,indicater,n,m,total,position : longint;
height,x,y,father,year,ans : array [-5..MAXN*MAXN] of longint;
map,reflect : array [-5..MAXN,-5..MAXN] of longint;
flag : boolean;
//height[],x[],y[]拍扁成序列后的节点信息
//map[],reflect[],转化成矩阵,以及矩阵中元素在序列里的映射
//year[],询问的年份
//ans[],对于每一个询问的答案
//flag记录指针是不是用尽了

Function getfather (x : longint) : longint;
begin
if x = father[x]
    then exit (x);
father[x] := getfather (father[x]);
exit (father[x]);
end;   //并查集

Procedure merge (x,y : longint);
var xx,yy : longint;
begin
xx := getfather (x);
yy := getfather (y);
father[xx] := yy;
if xx <> yy
    then dec (cnt);   //如果祖先不是同一个,连通块-1
end;

Procedure Swap (var x,y : longint);
var temp : longint;
begin
temp := x;x := y;y := temp;
end;

Procedure sort (l,r : longint);
var i,j,t,mid : longint;
begin
i := l;j := r;
mid := height[(l+r) >> 1];
repeat
    while height[i] > mid do
        inc (i);
    while height[j] < mid do
        dec (j);
    if i <= j
        then begin
                 swap (height[i],height[j]);
                 swap (x[i],x[j]);
                 swap (y[i],y[j]);
                 inc (i);dec (j);
             end;
until i > j;
if l < j
    then sort (l,j);
if i < r
    then sort (i,r);
end;    //从大到小排序

begin
readln (t);
for ii := 1 to t do
    begin
        readln (n,m);
        total := n * m;
        for i := 1 to n do
            begin
                for j := 1 to m do
                    begin
                        position := m * (i-1) + j;
                        //按顺位计算出拍扁后的编号
                        read (height[position]);
                        x[position] := i;
                        y[position] := j;
                        //保存信息
                    end;
                readln;
            end;
        sort (1,total);
        for i := 1 to total do
            begin
                map[x[i]][y[i]] := height[i];
                reflect[x[i]][y[i]] := i;
                father[i] := i;
                //映射,初始化并查集
            end;
        readln (q);
        for i := 1 to q do
            read (year[i]);  //读入询问
        indicater := q;      //指针初始化变成q
        cnt := 0;flag := false;
        fillchar (ans,sizeof (ans),0);
        for i := 1 to total do
            begin
                while height[i] <= year[indicater] do
                    begin
                        ans[indicater] := cnt;
                        dec (indicater);
                        if indicater = 0
                            then begin
                                     flag := true;
                                     break;
                                 end;
                    end;
                //如果当前高度要被漫过了,试着减小指针
                //如果减到0了,说明水漫金山了
                if flag
                    then break;
                //都水漫金山了,还玩蛇皮
                inc (cnt);   //连通块+1
                for j := 1 to 4 do
                    begin
                        xx := x[i] + direction[j,1];
                        yy := y[i] + direction[j,2];
                        if (xx >= 1) and (xx <= n) and (yy >= 1)
                           and (yy <= m) and (map[xx][yy] > year[indicater])
                           //没有越界且高度符合要求
                            then merge (reflect[xx][yy],i);//合并
                    end;
            end;
        if not flag
            then for i := indicater downto 1 do
                     ans[i] := cnt;
        //出来之后发现指针还没有用完,说明高度太高了
        //后面的连通块个数直接赋值
        for i := 1 to q do
            write (ans[i],' ');
        writeln;
    end;
end.   //撒花
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值