Islands
Time Limit: 30000/10000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others)
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×mn×m grid. Each grid field has a certain height. Unfortunately, the sea level started to raise and in year ii, the level is ii 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×54×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.
Input
Multiple Test Cases
The input contains several test cases. The first line of the input contains a positive integer Z≤20Z≤20,denoting the number of test cases. Then ZZ test cases follow, each conforming to the format described in section Single Instance Input. For each test case, your program has to write an output conforming to the format described in section Single Instance Output.
Single Instance Input
The first line contains two numbers nn and mm separated by a single space, the dimensions of the island, where 1≤n,m≤10001≤n,m≤1000. Next nn lines contain mm integers from the range [1,109][1,109] separated by single spaces, denoting the heights of the respective fields. Next line contains an integer TT (1≤T≤1051≤T≤105). The last line contains TT integers tjtj , separated by single spaces, such that 0≤t1≤t2≤⋯≤tT≤1090≤t1≤t2≤⋯≤tT≤109
Output
Single Instance Output
Your program should output a single line consisting of TT numbers rjrj , where rjrj is the number of unflooded areas in year tjtj . After every number ,you must output a single space!
Sample input and output
Sample Input | Sample Output |
---|---|
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
|
2 3 1 0 0
|
Source
题目大意:有一个岛屿是n*m的长方形的,每个格子的高度会给定,然后这个地方的水位会逐年上涨,在第i年时水位是i。就是说,所有高度<=i的格子都会被淹没。求没有被淹没的区域有几块(如果两个格子有公共边就认为它们属于同一个区域)。
现在给出n和m(1<=n,m<=1000),以及每个格子的高度([1,10^9]),然后有T([1,10^5])次查询,再给出t1,t2,,,(0,10^9),对于ti保证t这个序列是上升的。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <algorithm>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long Ull;
#define MM(a,b) memset(a,b,sizeof(a));
const double eps = 1e-10;
const int inf = 0x3f3f3f3f;
const double pi=acos(-1);
const int maxn=1000;
int t[100000+10],ans[100000+10];
int dx[5]={-1,1,0,0};
int dy[5]={0,0,1,-1};
int n,m;
struct node{
int x,y,h,id;
bool operator<(const node b) const
{
return this->h<b.h;//逆序考虑
}
}ne[maxn*maxn+10];
int par[maxn*maxn+10];
int findr(int id)
{
if(par[id]!=id)
par[id]=findr(par[id]);
return par[id];
}
void unite(int i,int j)
{
int ri=findr(i);
int rj=findr(j);
if(ri!=rj) par[ri]=rj;
}
int inmap(int x,int y)
{
return x>=1&&x<=n&&y>=1&&y<=m;
}
priority_queue<node> q;//元素多了的话优先队列还是要开到外面的
int main()
{
int cas;
scanf("%d",&cas);
while(cas--)
{
MM(ans,0);
scanf("%d %d",&n,&m);
int sz=0;
while(q.size()) q.pop();//清空
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&ne[++sz].h);
ne[sz].x=i;
ne[sz].y=j;
ne[sz].id=(i-1)*m+j;
par[sz]=ne[sz].id;
q.push(ne[sz]);
}
int w;
scanf("%d",&w);
for(int i=1;i<=w;i++) scanf("%d",&t[i]);
for(int k=w;k>=1;k--)
{
ans[k]=ans[k+1];//当前集合在后一时刻的基础上
while(q.size())
{
node cur=q.top();
if(cur.h<=t[k]) break;
q.pop();
ans[k]++;//每个节点刚出现时都是一个独立的
for(int i=0;i<=3;i++)
{
int x=cur.x+dx[i];
int y=cur.y+dy[i];
int id=(x-1)*m+y;
if(ne[id].h<=t[k]||!inmap(x,y)) continue;
int rx=findr(cur.id);//因为cur的父节点会随着合并而更新,所以需 //要再搜索一次
int ry=findr(id);
if(rx!=ry)
{
unite(rx,ry);
ans[k]--;//可以进行合并的话,集合数减1
}
}
}
}
for(int i=1;i<=w;i++)
printf("%d ",ans[i]);
printf("
");
}
return 0;
}
分析:并查集神题啊,暴搜肯定超时的,因为下一个时间的集合数是肯定在这一个时刻的集合的基础上减少的,所以可以考虑并查集,但是并查集是进行连接点的而不是删除点的,怎么办呢?把时间反过来逆向思考就好,最后还要注意,每次进行集合间的合并后,下次再用到该点时还要再查找一次。
其实可以用图来理解,每一个节点刚开始都是独立的,故增1,然后每一次合并时相当于连接了一条边,
集合数就会减1,因此一个集合最终只会增1,当然,如果已经在同一个集合的话就不能进行连接了,因为
不能产生环;
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <algorithm>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long Ull;
#define MM(a,b) memset(a,b,sizeof(a));
const double eps = 1e-10;
const int inf = 0x3f3f3f3f;
const double pi=acos(-1);
const int maxn=1000;
int t[100000+10],ans[100000+10];
int dx[5]={-1,1,0,0};
int dy[5]={0,0,1,-1};
int n,m;
struct node{
int x,y,h,id;
bool operator<(const node b) const
{
return this->h<b.h;
}
}ne[maxn*maxn+10];
int par[maxn*maxn+10];
int findr(int id)
{
if(par[id]!=id)
par[id]=findr(par[id]);
return par[id];
}
void unite(int i,int j)
{
int ri=findr(i);
int rj=findr(j);
if(ri!=rj) par[ri]=rj;
}
int main()
{
int cas;
scanf("%d",&cas);
while(cas--)
{
scanf("%d %d",&n,&m);
int sz=0;
priority_queue<node> q;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&ne[++sz].h);
ne[sz].x=i;
ne[sz].y=j;
ne[sz].id=(i-1)*m+j;
par[sz]=-1;
q.push(ne[sz]);
}
int w;
scanf("%d",&w);
for(int i=1;i<=w;i++) scanf("%d",&t[i]);
for(int k=w;k>=1;k--)
{
ans[k]=ans[k+1];
while(1)
{
node cur=q.top();
if(cur.h<=t[k]) break;
q.pop();
if(par[cur.id]==-1)
{
ans[k]++;
par[cur.id]=cur.id;
}
int rx=findr(cur.id);
for(int i=0;i<=3;i++)
{
int x=cur.x+dx[i];
int y=cur.y+dy[i];
int id=(x-1)*m+y;
int ry=findr(id);
if(ne[id].h<=t[i]) continue;
if(par[id]==-1) ans[k]++;
if(rx!=ry)
{
unite(rx,ry);
ans[k]--;
}
}
}
}
for(int i=1;i<=w;i++)
printf("%d ",ans[i]);
}
return 0;
}