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\n",ans);
return 0;
}