loj6177 「美团 CodeM 初赛 Round B」送外卖2 最短路+状压dp

题目传送门

https://loj.ac/problem/6177

题解

一直不知道允不允许这样的情况:取了第一的任务的货物后前往配送的时候,顺路取了第二个货物。

然后发现如果不可以这样的话,那么原题就是一个 \(O(n^3+q^2)\) 算法就能过的题,和数据范围远远不搭配。

所以显然是允许的。根据这个数据范围,我们很容易想到状压每一个任务目前的状态:要么是还没有被接货,要么是在运送途中,要么是运送完成,这三种情况。直接用三进制状压一下,设 \(dp[S][i]\) 表示达到 \(S\) 中的状态且最终停留在了 \(i\) 的最小合法时间。

转移直接枚举一下下一个去给哪一个任务取货或者配送就可以了。

最后的话,找到 \(f[S][i] \neq \infty\)\(S\)\(2\) 的个数最多的就行了。


代码如下:

#include<bits/stdc++.h>

#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back

template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}

typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;

template<typename I> inline void read(I &x) {
    int f = 0, c;
    while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
    x = c & 15;
    while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
    f ? x = -x : 0;
}

const int N = 20 + 3;
const int M = 400 + 7;
const int Q = 10 + 3;
const int INF = 0x3f3f3f3f;
const int NP = 59049 + 7;

int n, m, q, S;
int f[N][N], bin[N], dp[NP][N];

struct Task { int s, t, l, r; } a[N];

inline void floyd() {
    for (int k = 1; k <= n; ++k)
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                if (i != j) smin(f[i][j], f[i][k] + f[k][j]);
}

inline void DP() {
    bin[0] = 1;
    for (int i = 1; i <= q; ++i) bin[i] = bin[i - 1] * 3;
    S = bin[q] - 1;
    memset(dp, 0x3f, sizeof(dp));
    dp[0][1] = 0;
    for (int s = 0; s <= S; ++s) {
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= q; ++j) {
                if (s / bin[j - 1] % 3 == 0) smin(dp[s + bin[j - 1]][a[j].s], std::max(dp[s][i] + f[i][a[j].s], a[j].l));
                else if (s / bin[j - 1] % 3 == 1 && dp[s][i] + f[i][a[j].t] <= a[j].r) smin(dp[s + bin[j - 1]][a[j].t], dp[s][i] + f[i][a[j].t]);
            }
        }
    }
}

inline void work() {
    floyd();
    DP();
    int ans = 0;
    for (int s = 0; s <= S; ++s)
        for (int i = 1; i <= n; ++i) if (dp[s][i] != INF) {
            int cnt = 0, ss = s;
            while (ss) cnt += ss % 3 == 2, ss /= 3;
            smax(ans, cnt);
        }
    printf("%d\n", ans);
}

inline void init() {
    read(n), read(m), read(q);
    int x, y, z;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            if (i != j) f[i][j] = INF;
            else f[i][j] = 0;
    for (int i = 1; i <= m; ++i) read(x), read(y),  read(z), smin(f[x][y], z);
    for (int i = 1; i <= q; ++i) read(a[i].s), read(a[i].t), read(a[i].l), read(a[i].r);
}

int main() {
#ifdef hzhkk
    freopen("hkk.in", "r", stdin);
#endif
    init();
    work();
    fclose(stdin), fclose(stdout);
    return 0;
}

转载于:https://www.cnblogs.com/hankeke/p/lou6177.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值