0x00 前言
此题需要使用并查集来解决,题目标签的二分是假的吧…
0x01 思路
- 建边
此题的思路比较简单,首先建边,一个点的第一条边是它的右边那个点,第二条边是下面那个点,边的权值为两个点差的绝对值。
- 加边
首先需要对边进行一个排序,按照边的权值从小到大排序,因为我们需要让答案尽可能的小,然后依次枚举每一条边,如果这条边的两个点已经在一个集合内了,就不需要去管。如果两个点没有在一个集合内,就需要去连接,当两个集合的数量加起来大于了 t t t 那么就可以加答案,并且将这条边连接起来。
大致思路还是很简单,但是有十分多的细节。
0x02 技巧
- 数组范围
我们观察数据
n , m ≤ 500 n,m\le500 n,m≤500
因为我们需要用并查集来统计,所以可以把这个二维数据压缩成一维,那么点的个数就是 n × m n \times m n×m,因为每个点可以连接两条边,所以边的数组就需要开点的个数的两倍。
- 如何建边
我们可以用一个数组来标记当前点是第几个点,用一个变量去累加当前是第几个点就可以了,由于后面加边的需要,我们要开一个用来统计当前这个集合要作为起点的个数。然后求权值就很简单了。
- 如何加边
加边要注意的就是当两个集合的点的数量加起来大于等于 t t t 时,需要逐次判断两个区间,为什么呢?因为如果其中一个区间点的个数已经达到了 t t t,前面已经加过了,就不用再加了,然后合并的时候就按照并查集的基本操作合并就好。
0x03 代码
#include<bits/stdc++.h>
#define int long long
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define dep(i,r,l) for(int i=r;i>=l;i--)
using namespace std;
int n,m,t,tot,to,ans;
int fa[2500005];
int s[2500005];
int ma[505][505];
int bj[2500005];
int bh[505][505];
struct node{
int x,y,f;
}a[2500005];
bool cmp(node l,node r){return l.f<r.f;}
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
signed main(){
scanf("%lld %lld %lld",&n,&m,&t);
rep(i,1,n)
rep(j,1,m){
scanf("%lld",&ma[i][j]);
tot++;
bh[i][j]=tot;
}
rep(i,1,n){
rep(j,1,m){
int bjj;
scanf("%lld",&bjj);
if(bjj) bj[(i-1)*m+j]=1;
if(j!=m){
a[++to].f=abs(ma[i][j+1]-ma[i][j]);
a[to].x=bh[i][j];
a[to].y=bh[i][j+1];
}
if(i!=n){
a[++to].f=abs(ma[i+1][j]-ma[i][j]);
a[to].x=bh[i][j];
a[to].y=bh[i+1][j];
}
}
}
sort(a+1,a+1+to,cmp);
rep(i,1,tot*2){
fa[i]=i;
s[i]=1;
}
rep(i,1,to){
int x=find(a[i].x),y=find(a[i].y);
if(x==y) continue;
if(s[x]+s[y]>=t){
if(s[x]<t) ans+=a[i].f*bj[x];
if(s[y]<t) ans+=a[i].f*bj[y];
}
bj[x]+=bj[y];
s[x]+=s[y];
fa[y]=x;
}
printf("%lld",ans);
return 0;
}