【题解】AcWing 274.移动服务

AcWing 274.移动服务

题目描述

一个公司有三个移动服务员,最初分别在位置 1 , 2 , 3 1,2,3 123 处。

如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个地方去。

某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。

p p p q q q 移动一个员工,需要花费 c ( p , q ) c(p,q) c(p,q)

这个函数不一定对称,但保证 c ( p , p ) = 0 c(p,p)=0 c(p,p)=0

给出 N N N 个请求,请求发生的位置分别为 p 1 ∼ p N p_1∼p_N p1pN

公司必须按顺序依次满足所有请求,且过程中不能去其他额外的位置,目标是最小化公司花费,请你帮忙计算这个最小花费。

输入格式

1 1 1 行有两个整数 L , N L,N L,N,其中 L L L 是位置数量, N N N 是请求数量,每个位置从 1 1 1 L L L 编号。

2 2 2 L + 1 L+1 L+1 行每行包含 L L L 个非负整数,第 i + 1 i+1 i+1 行的第 j j j 个数表示 c ( i , j ) c(i,j) c(i,j),并且它小于 2000 2000 2000

最后一行包含 N N N 个整数,是请求列表。

一开始三个服务员分别在位置 1 , 2 , 3 1,2,3 123

输出格式

输出一个整数 M M M,表示最小花费。

数据范围

3 ≤ L ≤ 200 3≤L≤200 3L200,
1 ≤ N ≤ 1000 1≤N≤1000 1N1000

输入样例

5 9
0 1 1 1 1
1 0 2 3 2
1 1 0 4 1
2 1 5 0 1
4 2 3 4 0
4 2 4 1 5 4 3 2 1

输出样例

5

题目分析

这道题就是在 AcWing 271.杨老师的照相排列 中所说的,通过“一个状态应该更新哪些后续阶段的未知状态”得到状态转移方程的,这种方法比“如何计算出一个状态”思考起来更加自然、简便。

状态表示:设 f [ i , x , y , z ] f[i,x,y,z] f[i,x,y,z] 表示当前完成了第 i i i 个请求,且三位员工分别在 x , y , z x,y,z x,y,z 的所有途径中公司花费的最小值。

状态计算:(设第 i + 1 i+1 i+1 个请求在 u u u 处) (注意要判断是否有两个员工所处位置相同)

  1. x x x 位置的员工去完成第 i + 1 i+1 i+1 个请求, f [ i + 1 , u , y , z ] = m i n { f [ i + 1 , u , y , z ] , f [ i , x , y , z ] + c [ x , u ] } f[i+1,u,y,z]=min\{f[i+1,u,y,z], f[i,x,y,z]+c[x,u]\} f[i+1,u,y,z]=min{ f[i+1,u,y,z],f[i,x,y,z]+c[x,u]}
  2. y y y 位置的员工去完成第 i + 1 i+1 i+1 个请求, f [ i + 1 , x , u , z ] = m i n { f [ i + 1 , x , u , z ] , f [ i , x , y , z ] + c [ y , u ] } f[i+1,x,u,z]=min\{f[i+1,x,u,z], f[i,x,y,z]+c[y,u]\} f[i+1,x,u,z]=min{ f[i+1,x,u,z],f[i,x,y,z]+c[y,u]}
  3. z z z 位置的员工去完成第 i + 1 i+1 i+1 个请求, f [ i + 1 , x , y , u ] = m i n { f [ i + 1 , x , y , u ] , f [ i , x , y , z ] + c [ z , u ] } f[i+1,x,y,u]=min\{f[i+1,x,y,u], f[i,x,y,z]+c[z,u]\} f[i+1,x,y,u]=min{ f[i+1,x,y,u],f[i,x,y,z]+c[z,u]}

但是,上述 f f f 数组的空间复杂度为 O ( 1000 × 20 0 3 ) = O ( 8 × 1 0 9 ) O(1000×200^3)=O(8×10^9) O(1000×2003)=O(8×109),难以接受。

发现在完成第 i + 1 i+1 i+1 个请求时,总有一个员工在 u u u,因此可以不必表示这个员工所处位置,只需表示另外两个员工的位置即可。

状态表示 2 2 2:设 f [ i , x , y ] f[i,x,y] f[i,x,y] 表示当前完成了第 i i i 个请求,且一位员工在 p [ i ] p[i] p[i],另两位员工分别在 x , y x,y x,y 的所有途径中公司花费的最小值。

状态计算 2 2 2:(注意要判断是否有两个员工所处位置相同)

  1. x x x 位置的员工去完成第 i + 1 i+1 i+1 个请求, f [ i + 1 , p [ i ] , y ] = m i n { f [ i + 1 , p [ i ] , y ] , f [ i , x , y ] + c [ x , p [ i + 1 ] } f[i+1,p[i],y]=min\{f[i+1,p[i],y], f[i,x,y]+c[x,p[i+1]\} f[i+1,p[i],y]=min{ f[i+1,p[i],y],f[i,x,y]+c[x,p[i+1]}
  2. y y y 位置的员工去完成第 i + 1 i+1 i+1 个请求, f [ i + 1 , x , p [ i ] ] = m i n { f [ i + 1 , x , p [ i ] ] , f [ i , x , y ] + c [ y , p [ i + 1 ] ] } f[i+1,x,p[i]]=min\{f[i+1,x,p[i]], f[i,x,y]+c[y,p[i+1]]\} f[i+1,x,p[i]]=min{ f[i+1,x,p[i]],f[i,x,y]+c[y,p[i+1]]}
  3. p [ i ] p[i] p[i] 位置的员工去完成第 i + 1 i+1 i+1 个请求, f [ i + 1 , x , y ] = m i n { f [ i + 1 , x , y ] , f [ i , x , y ] + c [ p [ i ] , p [ i + 1 ] ] } f[i+1,x,y]=min\{f[i+1,x,y], f[i,x,y]+c[p[i],p[i+1]]\} f[i+1,x,y]=min{ f[i+1,x,y],f[i,x,y]+c[p[i],p[i+1]]}

状态初始化: f [ i , x , y ] = + ∞ , f [ 0 , 1 , 2 ] = 0 f[i,x,y]=+\infty,f[0,1,2]=0 f[i,x,y]=+,f[0,1,2]=0

代码:

#include <iostream>
#include <cstring>
using namespace std;

int l, n, res = 0x3f3f3f3f;
int c[210][210], p[1010], f[1010][210][210];

int main(){
    
    
    cin >> l >> n;
    for (int i = 1; i <= l; i ++)
        for (int j = 1; j <= l; j ++)
            cin >> c[i][j];
    for (int i = 1; i <= n; i ++) cin >> p[i];
    memset(f, 0x3f, sizeof(f));
    
    p[0] = 3;  //因为开始时三个员工分别在1,2,3位置,不妨设第0个请求在3位置
    f[0][1][2] = 0;  //初始状态为另两个员工在1,2位置
    for (int i = 0; i < n; i ++)
        for (int x = 1; x <= l; x ++)
            for (int y = 1; y <= l; y ++){
    
    
                int z = p[i], u = p[i + 1], v = f[i][x][y];
                if(x == y || x == z || y == z) continue;
                f[i + 1][x][y] = min(f[i + 1][x][y], v + c[z][u]);
                f[i + 1][x][z] = min(f[i + 1][x][z], v + c[y][u]);
                f[i + 1][y][z] = min(f[i + 1][y][z], v + c[x][u]);
            }
    
    for (int x = 1; x <= l; x ++)
        for (int y = 1; y <= l; y ++)
            res = min(res, f[n][x][y]);
        
    cout << res;
    return 0;
}

当然, f f f 数组的空间复杂度还是很大,虽然在大多数情况下已经可以AC,但这里还要介绍以下滚动数组:

  1. 滚动数组技巧: d p [ i dp[i dp[i& 1 ] = d p [ ( i − 1 ) 1]=dp[(i-1) 1]=dp[(i1)& 1 ] + ? 1]+? 1]+?
  2. 滚动数组需要及时清空不需要的位置信息;
  3. 滚动数组适用范围:某一状态 d p [ i ] dp[i] dp[i] 只与 d p [ i − 1 ] dp[i-1] dp[i1] 有关。

因此, f f f 数组的空间复杂度只要 O ( 2 × 20 0 2 ) O(2×200^2) O(2×2002) 即可 (时间复杂度也会大大降低,实测表明不用滚动数组总时间约 3 s 3s 3s,用滚动数组只要约 1.8 s 1.8s 1.8s)。

代码 (滚动数组):

#include <iostream>
#include <cstring>
using namespace std;

int l, n, res = 0x3f3f3f3f;
int c[210][210], p[1010], f[2][210][210];

int main(){
    
    
    cin >> l >> n;
    for (int i = 1; i <= l; i ++)
        for (int j = 1; j <= l; j ++)
            cin >> c[i][j];
    for (int i = 1; i <= n; i ++) cin >> p[i];
    memset(f, 0x3f, sizeof(f));
    
    p[0] = 3;
    f[0][1][2] = 0;
    for (int i = 0; i < n; i ++)
        for (int x = 1; x <= l; x ++)
            for (int y = 1; y <= l; y ++){
    
    
                int z = p[i], u = p[i + 1], &v = f[i & 1][x][y];
                if(x != y && x != z && y != z){
    
    
                    f[(i + 1) & 1][x][y] = min(f[(i + 1) & 1][x][y], v + c[z][u]);
                    f[(i + 1) & 1][x][z] = min(f[(i + 1) & 1][x][z], v + c[y][u]);
                    f[(i + 1) & 1][y][z] = min(f[(i + 1) & 1][y][z], v + c[x][u]);
                }
                v = 0x3f3f3f3f;  //注意每次用完都要初始化,所以上面不能用continue了
            }
    
    for (int x = 1; x <= l; x ++)
        for (int y = 1; y <= l; y ++)
            res = min(res, f[n & 1][x][y]);
        
    cout << res;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/f4u4u4r/article/details/121302638