【bzoj3485: [Baltic2012]peaks】并查集

3485: [Baltic2012]peaks

Time Limit: 40 Sec   Memory Limit: 64 MB
Submit: 32   Solved: 18
[ Submit][ Status][ Discuss]

Description

给定一个n*m的地图,每个格子都有一个高度,两个格子相邻当且仅当他们有公共点,每次可以从一个格子上可以走到另一个和他相邻的格子上。我们把一块高度相同并且八连通的格子的集合称之为一个平原,同时把相邻的格子中没有比他更高的点的平原成为峰。现在要你统计出所有的峰的高度和评估值,按高度为第一关键字,评估值为第二关键字从大到小输出。一个峰的评估值是指,如果从这个峰到一个比他高的峰去,所要经过的高度最小的平原的最大值。

Input

第一行给出,N,M。(1≤N,M≤2000, N×M≤10^5

接下来N行M列,描述这个矩阵,其中的数在[1,10^6]之间

Output

如题

Sample Input

6 6
21 16 9 11 6 7
21 21 10 14 15 9
18 20 8 9 13 14
11 10 9 9 8 13
8 12 12 14 13 8
7 13 12 9 5 1

Sample Output

4
21 0
15 11
14 13
13 12

HINT

Source




刚开始题意没看懂,wa了次,后来发现,一片连在一起的高度一样的格子算一块(斜着连也算)。

我们先处理出哪些点是峰,我们只要把每一块都dfs一遍,并且这一块旁边所有点的最大值小于等于这个块的值,那么说明这个块就是峰,选这个块中的一个点打上标记。

然后我们把格子按照格子中值的大小从大到小排序,然后一个个加格子:

每加入一个格子,我们把这个格子和周围的已经出现的格子连边,连边就相当于把两个并查集合并。我们在处理的时候特殊处理一下并查集的根,若是并查集中有打过标记的格子,那么把这个格子当做根,这样的话当两个都打过标记的根相遇,就可以进行更新了。

两个都打过标记的根相遇,如果不是一样大, 我们更新较小的那个根的答案,答案就是当前加入格子的权值,然后把这个根较小的接到根较大的并查集下面。

若是一样大,我们给这两个根连边,然后把一个加入另一个中,在全部更新完后,再把当时没更新的更新掉,两个能合并并且大小一样,那么答案肯定也是一样的,直接赋值就好。

最后把所有的峰都整理出来,排个序输出就可以。

如果看不懂的可以参照代码理解(代码挺丑的,因为刚开始题意理解错了,有点乱)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 2005
#define M 100505
using namespace std;
const int dx[8]={0,0,1,-1,1,1,-1,-1},dy[8]={1,-1,1,-1,-1,0,1,0};
int k,fir[M],n,m,a[N][N],d[M],fa[M],flag[M],cnt,cnt1,ans[M],vis[N][N],vis1[N][N];
struct ha{
	int r,nx;
}A[M];
struct he{
	int x,y,d,n;
}c[M];
struct he1{
	int ans,d;
}C[M];
queue<int> Q;
int gf(int x){
	if(fa[x]==x) return x;return fa[x]=gf(fa[x]);
}
bool check(int u){
	for(int i=0;i<8;i++){
		int x1=c[u].x+dx[i];
		int y1=c[u].y+dy[i];
		if(a[x1][y1]>a[c[u].x][c[u].y]) return 0;
		if(flag[(x1-1)*m+y1]==1) return 0; 
	}
	return 1;
}
void add(int l,int r){
	k++;A[k].r=r;A[k].nx=fir[l];fir[l]=k;
}
void modify(int u,int v,int w){
	int u1=gf(u),v1=gf(v);
	if(u1==v1) return ;
	if(flag[u1]&&flag[v1]){
		if(d[u1]>d[v1]){
			if(ans[v1]==0) ans[v1]=w;
			fa[v1]=u1;
		}else if( d[u1]<d[v1]){
			if(ans[u1]==0) ans[u1]=w;
			fa[u1]=v1;
		}else{
			add(v1,u1);
			fa[u1]=v1;
		}
	}else if(flag[u1]){
		fa[v1]=u1;
	}else {
		fa[u1]=v1;
	}
}
bool cmp(he a ,he b){
	return a.d>b.d;
}
bool cmp1(he1 a,he1 b){
	if(a.d==b.d) return a.ans>b.ans;
	return a.d>b.d;
}
void find(int x,int y,int &Mx){
	vis1[x][y]=1;
	for(int i=0;i<8;i++){
		int x1=x+dx[i];
		int y1=y+dy[i];
		Mx=max(Mx,a[x1][y1]);
		if(vis1[x1][y1]) continue;
		if(a[x1][y1]==a[x][y])find(x1,y1,Mx);
	}
}
int _read(){
    int sum=0;char ch=getchar();
    while(!(ch>='0'&&ch<='9'))ch=getchar();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=getchar();
    return sum;
} 
int main(){
	freopen("1.in","r",stdin);
	freopen("1.out","w",stdout);
	memset(fir,-1,sizeof(fir));
	n=_read();m=_read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			a[i][j]=_read();
			cnt++;
			fa[cnt]=cnt;
			c[cnt].x=i;c[cnt].y=j;c[cnt].d=a[i][j];c[cnt].n=(i-1)*m+j;d[c[cnt].n]=a[i][j];
		}
		sort(c+1,c+cnt+1,cmp);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				if(!vis1[i][j]) {
					int mx=-1;
					find(i,j,mx);
					if(mx<=a[i][j]) flag[(i-1)*m+j]=1;
				}
		for(int i=1;i<=cnt;i++){
			vis[c[i].x][c[i].y]=1;
			for(int j=0;j<8;j++){
				int x1=c[i].x+dx[j],y1=c[i].y+dy[j];
				if(1<=x1&&x1<=n&&1<=y1&&y1<=m&&vis[x1][y1])
					modify(c[i].n,(x1-1)*m+y1,c[i].d);
			}
		}
		for(int i=1;i<=cnt;i++)
			if(ans[i]!=0){
				Q.push(i);
			}
		while(!Q.empty()){
			cnt1++;
			int u=Q.front();Q.pop();
			C[cnt1].d=d[u];C[cnt1].ans=ans[u];
			for(int i=fir[u];i!=-1;i=A[i].nx){
				Q.push(A[i].r);ans[A[i].r]=ans[u];
			}
		}
		for(int i=1;i<=cnt;i++) 
		if(flag[i]==1&&ans[i]==0){
			cnt1++;C[cnt1].d=d[i];C[cnt1].ans=0;
		}
		sort(C+1,C+1+cnt1,cmp1);
		printf("%d\n",cnt1);
		for(int i=1;i<=cnt1;i++)
			printf("%d %d\n",C[i].d,C[i].ans);
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值