[USACO13MAR]The Cow Run GS 题解

题目传送门
附原题:

题目描述

Farmer John has forgotten to repair a hole in the fence on his farm, and his N cows (1 <= N <= 1,000) have escaped and gone on a rampage! Each minute a cow is outside the fence, she causes one dollar worth of damage. FJ must visit each cow to install a halter that will calm the cow and stop the damage.

Fortunately, the cows are positioned at distinct locations along a straight line on a road outside the farm. FJ knows the location P_i of each cow i (-500,000 <= P_i <= 500,000, P_i != 0) relative to the gate (position 0) where FJ starts.

FJ moves at one unit of distance per minute and can install a halter instantly. Please determine the order that FJ should visit the cows so he can minimize the total cost of the damage; you should compute the minimum total damage cost in this case.

农夫约翰的牧场围栏上出现了一个洞,有N(1 <= N <= 1,000)只牛从这个洞逃出了牧场。这些出逃的奶牛很狂躁,他们在外面到处搞破坏,每分钟每头牛都会给约翰带来1美元的损失。约翰必须用缰绳套住所有的牛,以停止他们搞破坏。

幸运的是,奶牛们都在牧场外一条笔直的公路上,牧场的大门恰好位于公里的0点处。约翰知道每头牛距离牧场大门的距离P_i(-500,000 <= P_i <= 500,000, P_i != 0)

约翰从农场大门出发,每分钟移动一个单位距离,每到一头牛所在的地点,约翰就会给它套上缰绳,套缰绳不花时间。按怎样的顺序去给牛套缰绳才能使约翰损失的费用最少?

输入格式

  • Line 1: The number of cows, N.

  • Lines 2..N+1: Line i+1 contains the integer P_i.

输出格式

  • Line 1: The minimum total cost of the damage.

输入输出样例

输入 #1
4 
-2 
-12 
3 
7 
输出 #1
50

说明/提示

Four cows placed in positions: -2, -12, 3, and 7.

The optimal visit order is -2, 3, 7, -12. FJ arrives at position -2 in 2 minutes for a total of 2 dollars in damage for that cow.

He then travels to position 3 (distance: 5) where the cumulative damage is 2 + 5 = 7 dollars for that cow.

He spends 4 more minutes to get to 7 at a cost of 7 + 4 = 11 dollars for that cow.

Finally, he spends 19 minutes to go to -12 with a cost of 11 + 19 = 30 dollars.

The total damage is 2 + 7 + 11 + 30 = 50 dollars.

题目解析

先看一眼题目,\(N\leq 1000\)\(O\left(N^2\right)\) 算法可以过。
我们可以发现,这道题目可以通过贪心或者搜索的方式来做,于是就可以小数据搜索大数据贪心
\(O\left(2^N\right)\)搜索肯定不行,保证TLE,那么我们考虑贪心。
贪心方案:向近一点的去,但是我们可以证明这是错的,我们构造一组数据如下:

4
-1 2 3 4

贪心是沿\(-1→2→3→4\)的道路走,耗费为\(1+4+5+6=16\)
但是存在一条\(2→3→4→-1\)的道路,耗费为\(5+2+3+4=14\)
明显贪心是错误的。

我们发现,这道题貌似可以用贪心,也貌似可以用搜索,并且只要求我们输出答案并且不输出路径,那么这题的正解是什么呢?显然是DP。

如果有一道题,可以用贪心,也可以用搜索,这么这道题目一定是DP。——FLYing

FLYing:我没说过
我谔谔

DP式解析:
我们采用区间DP,首先想到用区间DP,但是我们发现仅仅靠两个维度是不能完成这道题目的,我们令\(f_{i,j,k}\)来表示状态,\(i,j\)表示处理的区域的两个端点,\(k\)表示当前区间处理完成之后的位置,\(k=0\)代表在左端,\(k=1\)代表在右端。

代码版本1.0
\(f_{i,j,k}\)可以通过\(f_{i+1,j,k}\ f_{i,j-1,k}\)得到
不难推出状态转移式:

\[f_{i,j,0}=\min\left( f_{i+1,j,0}+|a_{i+1}-a_i|,f_{i+1,j,1}+|a_j-a_i|\right) \]

\[f_{i,j,0}=\min\left( f_{i,j-1,0}+|a_j-a_i|,f_{i,j-1,1}+|a_j-a_{j-1}|\right) \]

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int n,a[1039];
int f[1039][1039][3];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    f[0][0][0]=f[0][0][1]=0;
    for(int i=n;i>=1;i--)
        for(int j=i;j<=n;j++){
            if(i==j) f[i][j][0]=f[i][j][1]=abs(a[i]);
            else {
                f[i][j][0]=min( f[i+1][j][0]+abs(a[i+1]-a[i]) , f[i+1][j][1]+abs(a[i]-a[j]) );
                f[i][j][1]=min( f[i][j-1][0]+abs(a[j]-a[i])) , f[i][j-1][1]+abs(a[j]-a[j-1]) );
            }
        }
    printf("%d",min(f[1][n][0],f[1][n][1])-1);
    return 0;
}

然而,样例都过不去……

代码版本2.0
我们会发现1.0版本的DP式是错误的,因为我们发现,在前进的过程中,不仅仅只有一头奶牛在破坏。
所以正确的DP式是这样的:

\[f_{i,j,0}=\min\left( f_{i+1,j,0}+\left(n-j+i\right)\times|a_{i+1}-a_i|,f_{i+1,j,1}+\left(n-j+i\right)\times|a_j-a_i|\right) \]

\[f_{i,j,1}=\min\left( f_{i,j-1,0}+\left(n-j+i\right)\times|a_j-a_i|,f_{i,j-1,1}+\left(n-j+i\right)\times|a_j-a_{j-1}|\right) \]

代码就懒得贴了
然而样例还是过不去……

代码版本3.0
我们发现我们忽略的起点。
所以我们在进行排序的时候,把句子改成这样:sort(a+1,a+n+2)这样就可以把起点\(0\)算进去了,然后我们要找到起点的位置,当然,由于起点没有奶牛,所以DP式还要改一下:

\[f_{i,j,0}=\min\left( f_{i+1,j,0}+\left(1+n-j+i\right)\times|a_{i+1}-a_i|,f_{i+1,j,1}+\left(1+n-j+i\right)\times|a_j-a_i|\right) \]

\[f_{i,j,1}=\min\left( f_{i,j-1,0}+\left(1+n-j+i\right)\times|a_j-a_i|,f_{i,j-1,1}+\left(1+n-j+i\right)\times|a_j-a_{j-1}|\right) \]

代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#define min(a,b) ((a)>(b)?(b):(a))
using namespace std;
int n,a[1039],s;
int f[1039][1039][2];
int main(){
	//freopen("1.txt","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	a[n+1]=0;
	sort(a+1,a+n+2);
	for(int i=1;i<=n+1;i++)
	    if(!a[i])
	        s=i;
	f[s][s][0]=f[s][s][1]=0;
	for(int i=n+1;i>=1;i--)
		for(int j=i+1;j<=n+1;j++){
			//if(i==j) f[i][j][0]=f[i][j][1]=abs(a[i]);
			//else {
				f[i][j][0]=min( f[i+1][j][0]+(a[i+1]-a[i])*(n-j+i+1) , f[i+1][j][1]+(a[j]-a[i])*(n-j+i+1) );
				f[i][j][1]=min( f[i][j-1][0]+(a[j]-a[i])*(n-j+i+1) , f[i][j-1][1]+(a[j]-a[j-1])*(n-j+i+1) );
			//}
		}
	printf("%d",min(f[1][n+1][0],f[1][n+1][1]));
	return 0;
}

然而样例还是过不去……
我太难了

代码版本4.0
我们发现,在DP式中有一个东西是这样的:\(n-i+j+1\)这个是奶牛的个数,但是,我们发现,如果\(i>s\)\(s\)为起点),那么我们发现,这个区间是没有意义的,而且这个DP式也是错的,所以我们的for循环要改一下:

for(int i=s;i>=1;i--)
		for(int j=i+1;j<=n+1;j++)

为了在计算中不要调用到无意义的区间,我们还需要把数组\(f\)赋值成\(\operatorname{INF}\),于是我们加上一句话。memset(f,0x3f,sizeof(f));
然后就可以过样例了,其实也AC了。
献上AC代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#define min(a,b) ((a)>(b)?(b):(a))
using namespace std;
int n,a[1039],s;
int f[1039][1039][2];
int main(){
	//freopen("1.txt","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	a[n+1]=0;
	sort(a+1,a+n+2);
	for(int i=1;i<=n+1;i++)
	    if(!a[i])
	        s=i;
	memset(f, 0x3f, sizeof(f));
	f[s][s][0]=f[s][s][1]=0;
	for(int i=s;i>=1;i--)
		for(int j=i+1;j<=n+1;j++){
			//if(i==j) f[i][j][0]=f[i][j][1]=abs(a[i]);
			//else {
				f[i][j][0]=min( f[i+1][j][0]+(a[i+1]-a[i])*(n-j+i+1) , f[i+1][j][1]+(a[j]-a[i])*(n-j+i+1) );
				f[i][j][1]=min( f[i][j-1][0]+(a[j]-a[i])*(n-j+i+1) , f[i][j-1][1]+(a[j]-a[j-1])*(n-j+i+1) );
			//}
		}
	printf("%d",min(f[1][n+1][0],f[1][n+1][1]));
	return 0;
}
posted @ 2021-02-15 21:36  jiangtaizhe001  阅读(138)  评论(0)    收藏  举报