[SMOJ1770]中国移动

97 篇文章 0 订阅
7 篇文章 0 订阅

题目描述

一个公司有三个移动服务员。如果某个地方有一个请求,某个员工必须赶到那个地方去(那个地方没有其他员工),某一时刻只有一个员工能移动。被请求后,他才能移动,不允许在同样的位置出现两个员工。从 p q 移动一个员工,需要花费 c(p,q) 。这个函数没有必要对称,但是 c(p,p)=0 。公司必须满足所有的请求。目标是最小化公司花费。

输入格式 1770.in

第一行有两个整数 L , N ( 3L200 , 1N1000 )。 L 是位置数 ;N 是请求数。每个位置从 1 到 L 编号。
L 行每行包含 L 个非负整数。第 i+1 行的第 j 个数表示 c(i,j) ,并且它小于2000。
最后一行包含 N 个数,是请求列表。一开始三个服务员分别在位置1,2,3。

输出格式 1770.out

一个数 M,表示最小服务花费。

输入样例 1770.in

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

输出样例 1770.out

5


这题不妨可以类比一下之前做过的多滋味的咖啡一题,也是一种类似的多维分配型 dp。(这个名词是我自己yy出来的,不知道是不是对的)

有的人可能反应不够灵活(比如我……),一看题不会想到 dp,会先写个贪心,然而只有 10 分。

实际上这题为什么能 dp 呢?我们不仅要知道一道题怎么做,更要明白为什么能够这么做。只有把握了题目、算法和数据结构之间的关系,才能更好解题。

其实很简单。因为请求是有序的,而且同一时刻还只有一个人在移动,显然具有阶段性,而且无后效性。

最直观的做法,可以用 f[i][A][B][C] 表示处理完请求i之后,三名服务员分别在位置 A B C 的最小费用。

转移也不是很难,设第 i 次请求位置为 x ,那么就有:
f[i][A][B][x]=min(f[i1][A][B][C]+cost[C][x])
f[i][A][x][C]=min(f[i1][A][B][C]+cost[B][x])
f[i][x][B][C]=min(f[i1][A][B][C]+cost[A][x])

很遗憾这样的做法既会 MLE 又会 TLE。那么必然要把状态改进一下。

我们结合题意,再观察一下上面的状态转移方程,可以发现,处理完请求 i 之后,三个服务员中必定有一人在 x

那么,我们为什么要多花一维状态记他呢??

直接可以记 f[i][A][B] ,表示处理完请求 i 之后,三名服务员(无序)分别在位置 A B x 的最小费用。

设第 i1 次请求位置为 y ,于是状态转移方程就变成了:
f[i][B][C]=dp[i1][B][C]+c[y][x]
f[i][B][y]=min(dp[i1][B][C]+c[C][x])
f[i][C][y]=min(dp[i1][B][C]+c[B][x])

转移的时候注意,其中 A B x 互不相等。

这样交上去几乎就是一个正解,很可惜却只有 95 分。

再来改进。既然 f[i] 的值只取决于 f[i1] ,显然可以滚动一下。这样就能 AC 了。

最后总结一下这题,其实做 dp 题最重要的还是如何划分阶段,恰当的描述状态,并进行合理的状态优化

参考代码:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

const int maxl = 200 + 10;
const int maxn = 1e3 + 10;

int l, n;
int c[maxl][maxl], ask[maxn];

int dp[2][maxl][maxl];

int main(void) {
    freopen("1770.in", "r", stdin);
    freopen("1770.out", "w", stdout);

    scanf("%d%d", &l, &n);
    for (int i = 1; i <= l; i++)
        for (int j = 1; j <= l; j++)
            scanf("%d", &c[i][j]);
    for (int i = 1; i <= n; i++) scanf("%d", &ask[i]);

    memset(dp, 0x7f, sizeof dp);
    //初始化状态
    dp[1][1][2] = dp[1][2][1] = c[3][ask[1]];
    dp[1][1][3] = dp[1][3][1] = c[2][ask[1]];
    dp[1][2][3] = dp[1][3][2] = c[1][ask[1]];

    for (int i = 2; i <= n; i++) {
        memset(dp[i & 1], 0x7f, sizeof dp[i & 1]); //务必清空,否则可能因为前面的值而造成影响
        for (int j = 1; j <= l; j++)
            if (j != ask[i - 1]) //检查限制,下同
                for (int k = 1; k <= l; k++)
                    if (k != j && k != ask[i - 1]) {
                        //printf("%d %d %d %d\n", i, j, k, dp[(i - 1) & 1][j][k]);
                        dp[i & 1][j][k]          = dp[i & 1][k][j]          = min(dp[i & 1][j][k]         , dp[(i - 1) & 1][j][k] + c[ask[i - 1]][ask[i]]);
                        dp[i & 1][j][ask[i - 1]] = dp[i & 1][ask[i - 1]][j] = min(dp[i & 1][j][ask[i - 1]], dp[(i - 1) & 1][j][k] + c[k][ask[i]]);
                        dp[i & 1][k][ask[i - 1]] = dp[i & 1][ask[i - 1]][k] = min(dp[i & 1][k][ask[i - 1]], dp[(i - 1) & 1][j][k] + c[j][ask[i]]);
                        //因为无序,所以j和k实际上是有对称性的
                    }
    }

    int ans = 0x7fffffff;
    for (int i = 1; i <= l; i++)
        if (i != ask[n])
        for (int j = 1; j <= l; j++)
            if (i != j && j != ask[n]) ans = min(ans, dp[n & 1][i][j]);

    printf("%d\n", ans);

    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值