P3101 [USACO14JAN] Ski Course Rating G

发布时间 2023-09-13 10:51:31作者: One_JuRuo

思路

晃一眼题目,这不和这道题一样吗?

但是再仔细一看,有有些不一样,要求了起点至少到多少个点,不要求起点互通,求的也不是最小的 \(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);
}