星星之火OIer:篱笆题解

本文介绍了篱笆题目3075的解题思路,从暴力求解到最小生成树优化,再到最终的贪心算法。在最小生成树方法中,通过优化处理2000*2000规模的数据。然而,贪心策略成为关键,首先确保至少打通一行一列的篱笆,每次选择减少乘积的操作。详细解题过程揭示了如何在编程竞赛中高效解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

篱笆题目出处(3075)

这道题是我们编程社考试中第三次的第二题,当时都有一些思路,但是时间紧,没打出来

读了题之后,第一思路是暴力,然后是最小生成树,最后是才想到贪心

首先,暴搜肯定会超时

然后,用最小生成树在小数据时加点优化可以勉强卡过

但是贪心才是正解

先来讲一讲最小生成树做法

在m和n都小于2000时可以卡过

大致思路:

以所有篱笆的交点作为节点,以篱笆的长度作为边权

然后就是求一个最小生成树

但是2000*2000=4000000,nlogn的算法会超时

这时,我们便要优化一下

因为在每一列或者每一行的篱笆的长度都是相同的

所以我们只需要从每一段抽出一个样本来排序就可以了

下面是同校机房大佬的一个最小生成树做法:

PS:勉强可以卡过,但数据大了会RE

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 2001
 
int n,m,a[MAXN+10],b[MAXN+10],fa[MAXN*MAXN+10];
long long ans;
void read()//读入部分
{
    int A,B;
    scanf("%d%d%d%d",&A,&B,&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    a[n+1]=A-a[n];
    ++n;
    for(int i=n-1;i>=1;i--)
        a[i]-=a[i-1];
    for(int i=1;i<=m;i++)//计算横着的边权
        scanf("%d",&b[i]);
    sort(b+1,b+m+1);
    b[m+1]=B-b[m];
    ++m;
    for(int i=m-1;i>=1;i--)
        b[i]-=b[i-1];//计算竖着的边权
    sort(a+1,a+n+1);
    sort(b+1,b+m+1);//对边权从小到大排序
}
int find(int x){
    if(fa[x]==x) return x;
    return find(fa[x]);
}//最小生成树
void workout()
{
    for(int i=1;i<=n*m;i++)
        fa[i]=i;
    for(int i=1,j=1;i<=n||j<=m;){
        if(i<=n&&(j>m||a[i]<b[j])){
            for(int k=1,num,x,y;k<m;k++){
                num=(i-1)*m+k;
                x=find(num),y=find(num+1);
                if(x!=y){
                    fa[y]=x;
                    ans+=1LL*a[i];
                }
            }
            i++;
        }
        else{
            for(int k=1,num,x,y;k<n;k++){
                num=(k-1)*m+j;
                x=find(num),y=find(num+m);
                if(x!=y){
                    fa[y]=x;
                    ans+=1LL*b[j];
                }
            }
            j++;
        }
    }//循环那个加进去会小一些
    printf("%lld\n",ans);
}
int main()
{
    read();
    workout();
    return 0;
}

我解释得不是很清楚,这里有比这个更清楚的同校机房大佬的博客

然后就是贪心的做法了

大致思路:

首先,我们需要至少把一列和一行的篱笆全部打通

然后每一次打通篱笆之后我们就要少乘一个

比如

对于这个图,当我们打通一些之后,现在要打蓝色箭头的这一个:

那我们发现我们只用打三个了

那我们就由此可以打出正解::

#include<cstdio>
#include<algorithm>
using namespace std;
inline void read(int &x) {
    x=0;
    int f=1;
    char s=getchar();
    while(s<'0'||s>'9') {
        if(s=='-')
            f=-1;
        s=getchar();
    }
    while(s>='0'&&s<='9') {
        x=x*10+s-48;
        s=getchar();
    }
    x*=f;
}
inline void pr(long long x) {
    if(x<0) {
        putchar('-');
        x=-x;
    }
    if(x>9)
        pr(x/10);
    putchar(x%10+48);
}//快读快输不解释
int A,B,a[2005],b[2005],i,n,j,k,m,x=1,y=1;
long long ans;
int main() {
    read(A),read(B),read(n),read(m);
    for(i=1;i<=n;i++)
        read(a[i]);
    for(i=1;i<=m;i++)
        read(b[i]);
    sort(a+1,a+1+n);
    sort(b+1,b+1+m);//先要对每一块篱笆的位置进行排序
    a[n+1]=A;
    b[m+1]=B;//方便计算
    for(i=0;i<=n;i++)
        a[i]=a[i+1]-a[i];
    for(i=0;i<=m;i++)
        b[i]=b[i+1]-b[i];//计算每段篱笆的距离
    sort(a,a+1+n);
    sort(b,b+1+m);//对距离排序,每次都要求最小的
    ans=a[0]*m+b[0]*n;//最短的距离
    while(x<=n&&y<=m)//当有一个打通了,其他的就都打通了
        if(a[x]<=b[y])//先切断小的
            ans+=a[x++]*(m-y+1);
        else
            ans+=b[y++]*(n-x+1);
    pr(ans);
}

大概就是这个样子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值