籬笆題目出處(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);
}
大概就是這個樣子