星星之火OIer:籬笆題解

籬笆題目出處(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);
}

大概就是這個樣子

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章