P5687 [CSP-SJX2019]网格图

题目传送门

思路

首先非常简单的(O(n^2))暴力很容易想,但是拿不到满分。看到最小生成树,首先就想到了(kruscal),但点的总数已经是(n*m),所以普通的过不了。发现特殊性质就是一行或者一列都是一个边权,肯定要通过这个性质来优化,但是我想了一天还是没想出来,太菜了。看了题解才发现原来是对这个算法理解不深,还是没有吃透原理。

首先(kruscal)的原理就是贪心,把边权排序然后一个个加边。此题也是同理,只不过将每一行和每一列当成一条边来排序然后加边。注意到如果只有列或者只有行是不可能把所有的点连起来的,所以可以先把最小的那一行和最小的那一列加入答案,然后排序模拟。但是发现还有一个难题,就是要避免环的情况,就是要避免重复计算导致答案过大。假设相邻两行和任意两列围成了一个大矩阵,然后右边的那一列和下边的一行边权较小,那么就把它们加入答案。然后考虑到一个大矩阵需要且只需要连三条边就可以联通,而如果连了左边的一行,右边的一行连起来对上下相邻两列其实已经没有意义了。因为右边的一行已经联通了上下,所以在确定了列的大小关系之后,这一行方格(被两行夹住的)就不会再用到左边的边了。所以减去1再乘上边权。本来我看不太懂别人写的题解,想自己写一篇明白的,但好像还是很迷惑,只要懂了就很简单,但是不懂是真的啥也不会。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;	
}
int n,m;
ll a[300005],b[300005];
ll ans;
int main()
{
	scanf("%d%d",&n,&m);	
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	for(int i=1;i<=m;i++){
		scanf("%lld",&b[i]);
	}
	sort(a+1,a+1+n);
	sort(b+1,b+1+m);
	ans+=a[1]*(m-1)+b[1]*(n-1);
	int sum1=1,sum2=1,a1=2,b1=2;//记录已经连起来的边和列
	while(a1<=n&&b1<=m){
		if(a[a1]<=b[b1]){
			ans+=(m-sum1)*a[a1];
			a1++;
			sum2++;//连起来一行,则列的贡献-1
		}
		else{
			ans+=(n-sum2)*b[b1];
			b1++;
			sum1++;
		}
	}
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/57xmz/p/13713492.html