首页 > 技术文章 > P5687 [CSP-SJX2019]网格图 题解

lbssxz 2020-09-07 15:06 原文

题目链接

1.题外话

停课了。刷刷之前的题可以RP++;

2.解题意

n*m,很规则的期盼图。

a[]表示水平相邻两个点之间点的边权,b[]则是竖着两个点之间的边权值。

每一个都只给你n/m个数据,表明这一行/列的边权值全都相同。

题目就是让你求这么一个图的最小生成树的边权值之和。

3.找思路

习惯性的浏览一下数据范围。如果暴力的话克鲁斯卡尔肯定是跑不过的,只能拿部分分。想要拿满分,肯定要用到题目给我们的特殊性质。

考虑一下克鲁斯卡尔的原理:边权从小到大排序,如果遇到能成环的就跳过,否则就把边加入到最小生成树中。

对于这道题,因为它一行,一列上的边权全部相同,那么他们被加入到最小生成树的概率是等价的。

如果你假设每一行的边都加入了最小生成树,那么此时一定存在一个扩充生成树的方案:

把列中权值最小的竖着的边加入进去,如下图(红色边表示已经被加入到最小生成树中):

 

 选取权值最小的一列加入:

 

这样一来,才是最小生成树。

 同样,对于列都选满的情况,权值最小的行也一定会被加入到生成树中来。

那么首先,sort一遍a,b,把权值最小的(下标为1)的边加入到最小生成树中来。因为这些边是一定会加入进来的;

这里的加边是可以降低复杂度的,因为一行/一列会被同时加入进来。


 

考虑之后的做法:

一个显然的想法是从行列综合考虑,按照权值从小到大加如一行/一列边。因为如果不选这个权值较小的,sort后下标更大的边将不会比它更优;

然后呢我们遇到一个很明显的阻碍:判环。

解决方法也很简单:我们不在一整列一整行地加入进入,而是除去那些 连接之前已经被加入的行/列的边后加入。

语言描述不当,画图理解来补:

假如我们已经选好了行和列中最优的:

 

 接下来我们发现a[2]>b[2],也就是这时候列比行更优,我们尝试尽可能多的加入这一行:

(注意:b[2]不一定是第二列哦)

 

 因为第三行是“之前已经被加入的行”,那么我们必须要舍去其中一条连接它与当前这一列。也就是说,图中绿色的列要舍去一个。由于我们要的答案是权值和而不是具体的选择方案,那么这两条边选哪个也就无所谓了。计算权值时我们只需把边数减一即可。

就这样进行下去,直到n行/m列其中一个被选满为止。宣告结束,最小生成树生成。

4.代码

#include<cstdio>
#include<algorithm>
#define N 300007
#define ll long long
using namespace std;
int n,m,a[N],b[N];
ll ans=0;
inline int read()
{
    int ans=0;
    char ch=getchar(),last=' ';
    while(ch>'9'||ch<'0')last=ch,ch=getchar();
    while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
    return last=='-'?-ans:ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
    }
    for(int i=1;i<=m;i++)
    {
        b[i]=read();
    }
    sort(a+1,a+1+n);
    sort(b+1,b+1+m);
    ans+=(ll)a[1]*(m-1)+(ll)b[1]*(n-1);
    int l=1,h=1,cnt1=2,cnt2=2;
    while(cnt1<=n&&cnt2<=m)
    {
        if(a[cnt1]<=b[cnt2])ans+=(ll)a[cnt1++]*(m-l),h++;
        else ans+=(ll)b[cnt2++]*(n-h),l++;
    }
    printf("%lld",ans);
}

希望对各位有所帮助。

祝NOIP2020rp++

推荐阅读