题目大意:
- 给定(n)个元素,每一个元素有(x_i,a_i,b_i,c_i,d_i)。
- 求一个(1)~(n)的全排列(p_1,p_2,dots,p_n)使得其价值最小,其中(p_1=s,p_n=e)。
- 定义一个排列的价值为(sum_{i=1}^{n-1}f(p_i,p_{i+1}))。
- 函数(f(i,j))定义为(f(i,j)=left {egin{array}{}x_i-x_j+c_i+b_j(i>j)\x_j-x_i+d_i+a_j(i<j) end{array} ight.)。
- 求所有满足条件排列的最小价值。
题目链接:704B. Ant Man。
题解:在原题中,很容易考虑到将(a_i)变为(a_i-x_i),(b_i,c_i,d_i)以此类推,所以就可以消除(x_i),仅考虑(a_i,b_i,c_i,d_i)对答案的影响。
考虑 DP。设(f_{i,j})表示从小到大选择了前(i)个数,一共分成了(j)个连续段的最小价值,然后对于每加入一个数,考虑它自己单独作为一段,连在一个段左端,连在一个段右端,连在两个段中间四种情况来考虑。对于起点和终点,只需要分两个段考虑即可。转移方程自己在草稿纸上画一下应该就能出来。
但是这一道题有一个比较麻烦的地方,就是起点不能往左合并,终点不能往右合并,这一种情况要考虑全,然后在转移中通过特判删掉这种转移方法即可。
时间复杂度(o(n^2))。
据说还有更加快速的贪心算法,但是我没有考虑了。
下面是代码。
#include <cstdio>
#include <cstring>
template<typename Elem>
Elem min(Elem a,Elem b){
return a<b?a:b;
}
typedef long long ll;
const int Maxn=5000;
const ll Inf=0x3f3f3f3f3f3f3f3fll;
ll f[Maxn+5][Maxn+5];
int n,s,e;
int x[Maxn+5],a[Maxn+5],b[Maxn+5],c[Maxn+5],d[Maxn+5];
int main(){
scanf("%d%d%d",&n,&s,&e);
for(int i=1;i<=n;i++){
scanf("%d",&x[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&b[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&c[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&d[i]);
}
for(int i=1;i<=n;i++){
a[i]+=x[i];
c[i]+=x[i];
b[i]-=x[i];
d[i]-=x[i];
}
memset(f,0x3f,sizeof f);
f[0][0]=0;
for(int i=1;i<=n;i++){
if(i!=s&&i!=e){
for(int j=1;j<=i;j++){
f[i][j]=min(f[i][j],f[i-1][j-1]+b[i]+d[i]);
if(j>(i>e)||i==n){
f[i][j]=min(f[i][j],f[i-1][j]+a[i]+d[i]);
}
if(j>(i>s)||i==n){
f[i][j]=min(f[i][j],f[i-1][j]+b[i]+c[i]);
}
if(j+1>(i>e)+(i>s)||i==n){
f[i][j]=min(f[i][j],f[i-1][j+1]+a[i]+c[i]);
}
}
}
else if(i==s){
for(int j=1;j<=i;j++){
if(j>(i>e)||i==n){
f[i][j]=min(f[i][j],f[i-1][j-1]+d[i]);
f[i][j]=min(f[i][j],f[i-1][j]+c[i]);
}
}
}
else{
for(int j=1;j<=i;j++){
if(j>(i>s)||i==n){
f[i][j]=min(f[i][j],f[i-1][j-1]+b[i]);
f[i][j]=min(f[i][j],f[i-1][j]+a[i]);
}
}
}
}
printf("%lld
",f[n][1]);
return 0;
}