思路
晃一眼题目,这不和这道题一样吗?
但是再仔细一看,有有些不一样,要求了起点至少到多少个点,不要求起点互通,求的也不是最小的 \(d\),而是 \(d\) 之和。
首先,很容易地发现,这道题再去二分肯定不现实,每个点都去二分一次,所需要的时间也很恐怖。
所以我们尝试从其他的方面入手。
可以发现,如果一个连通块的长度符合条件,那么整个连通块的点都符合条件,那么我们能否在更新并查集的时候,顺便维护这个并查集里的起点的个数呢。
这样的话,只要这个并查集符合条件,那么就直接取出这个并查集的起点个数,乘以答案,再把这个连通块的起点个数改为 \(0\) 就好。
那么怎么获得答案呢?
考虑对所有的边按照权值从小到大排序,权值就是两个点的高度差,那么这样的话,我们就先加权值小的边,这样就可以尽可能地使 \(d\) 变小了。
另外,因为是二维图,为了方便,我们采用二维转一维。
AC code
#include<bits/stdc++.h>
using namespace std;
struct edge{long long u,v,w;}b[500005];
inline bool cmp(edge a,edge b){return a.w<b.w;}//边排序
long long rt,n,m,T,h[505][505],idx,sum,fa[250005],siz[250005],ans[250005],k;
long long find(long long x){return (fa[x]==x)?x:fa[x]=find(fa[x]);}//并查集
inline long long get(long long i,long long j){return (i-1)*m+j;}//二维转一维
int main()
{
scanf("%lld%lld%lld",&n,&m,&T);
for(long long i=1;i<=n;++i)
for(long long j=1;j<=m;++j)
{
scanf("%lld",&h[i][j]);
if(i>1) b[++idx].u=get(i-1,j),b[idx].v=get(i,j),b[idx].w=abs(h[i][j]-h[i-1][j]);
if(j>1) b[++idx].u=get(i,j-1),b[idx].v=get(i,j),b[idx].w=abs(h[i][j]-h[i][j-1]);//建边
}
sort(b+1,b+idx+1,cmp);//排序
for(long long i=1;i<=n;++i) for(long long j=1;j<=m;++j){scanf("%lld",&ans[get(i,j)]),k+=ans[get(i,j)];}
if(T==1) printf("%lld",k),exit(0);//特殊情况
for(long long i=1;i<=n*m;++i) siz[i]=1,fa[i]=i;//初始化
for(long long i=1;i<=idx;++i)
if(find(b[i].u)!=find(b[i].v))
{
siz[find(b[i].v)]+=siz[find(b[i].u)],ans[find(b[i].v)]+=ans[find(b[i].u)];
if(siz[find(b[i].v)]>=T) sum+=ans[find(b[i].v)]*b[i].w,ans[find(b[i].v)]=0;//记得把起点个数清零,否则会重复统计
fa[find(b[i].u)]=find(b[i].v);//这个要放在最后
}
printf("%lld",sum);
}
标签:Rating,idx,get,P3101,查集,long,Ski,find,起点
From: https://www.cnblogs.com/One-JuRuo/p/17698935.html