LibreOj #539. 「LibreOJ NOIP Round #1」旅游路线

题目链接

做完这道题,我深知当一个问题复杂度过高的时候,把一些可以分离的操作都分散开,可以大幅度降低复杂度.....


发现无论有多少钱,每到一个点后扩展到的距离被限制在 \(min(C, c[i])\)边内,故可对此设计 \(DP\)

由于 \(D\) 很大,不妨将其设为 \(DP\) 的价值,用的钱设置为容量。

所以我们只需要枚举那些需要加油的点,用最优性取跳即可。

Step 1: 快速求出从 \(u\)\(v\) 不超过 \(c[i]\) 条边的最大距离

\(g[u][v][k]\) 表示从 \(u\) 走到 \(v\) 不超过 \(2 ^ k\) 条边走的最远距离。

注意,这里 \(K\) 的最大值是 \(log_2C\),因为最多扩展 \(C\) 条边。

\(O(N^3K)\) 可以预处理来这个玩意,递推式:

初始状态 \(g[u][v][0] = d[u][v]\)

\(g[u][v][k] = max(g[u][x][k - 1] + g[x][v][k - 1])\)


\(w[u][v]\) 表示从 \(u\) 跑到 \(v\) 不超过 \(min(C, c[i])\) 条边的最长距离。

即在 \(u\) 加油后跑到 \(v\) 的最长距离。

这个东西可以枚举 \(min(C, c[i])\) 的二进制位,用多个 \(1\) 拼起来。

具体转移式:

\(w[u][v] = max(last[u][x] + g[x][v][k])\)


Step 2:大力转移!

\(f[i][j]\) 为从 \(i\) 出发,用不超过 \(j\) 块钱能扩展到的最大距离。

状态转移方程:

\(f[u][j] = max(w[u][v])\)

\(f[u][q] = max(w[u][x] + f[x][q - p[x]])\)


Step 3:Ans!

显然,对于一个 \(u\)\(f[u][j] (0 <= j <= q)\) 是递增序列的。

那么我们需要找到一个尽量小的 \(j\),使得 \(f[u][j] >= d\)

用二分不就行了?。

时间复杂度 \(O(N^3log_C + N ^ 4 + T(log_2N^2))\)

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 105, M = 1005, L = 17;
int n, m, C, T, g[N][N][L];
int w[N][N], tmp[N], f[N][N * N];
int p[N], c[N];
int main() {
    memset(g, -0x3f, sizeof g);
    memset(w, -0x3f, sizeof w);
    scanf("%d%d%d%d", &n, &m, &C, &T);
    for (int i = 1; i <= n; i++) g[i][i][0] = 0;
    for (int i = 1; i <= n; i++) scanf("%d%d", p + i, c + i), c[i] = min(c[i], C);
    for (int i = 1, u, v, w; i <= m; i++) {
        scanf("%d%d%d", &u, &v, &w);
        g[u][v][0] = max(g[u][v][0], w);
    }

    for (int k = 1; k < L; k++) 
        for (int u = 1; u <= n; u++)
            for (int v = 1; v <= n; v++)
                for (int x = 1; x <= n; x++)
                    g[u][v][k] = max(g[u][v][k], g[u][x][k - 1] + g[x][v][k - 1]);
    

    for (int u = 1; u <= n; u++) {
        bool flag = true;
        for (int k = 0; k < L; k++) {
            if(c[u] >> k & 1) {
                if(flag) {
                    for (int v = 1; v <= n; v++) {
                        w[u][v] = tmp[v] = g[u][v][k];
                    }
                    flag = false;
                    continue;
                }

                for (int v = 1; v <= n; v++)
                    for (int x = 1; x <= n; x++)
                        w[u][v] = max(w[u][v], tmp[x] + g[x][v][k]);

                for (int v = 1; v <= n; v++) tmp[v] = w[u][v];
            }
        }
    } 

    for (int q = 0; q <= n * n; q++) {
        for (int u = 1; u <= n; u++) {
            for (int v = 1; v <= n; v++) {
                f[u][q] = max(f[u][q], w[u][v]);
                if(q >= p[v]) f[u][q] = max(f[u][q], w[u][v] + f[v][q - p[v]]);
            }
        }
    }
    
   
    
    for (int i = 1, s, q, d; i <= T; i++) {
        scanf("%d%d%d", &s, &q, &d);
        int l = p[s], r = q;
        if(r < l || f[s][r - l] < d) {
            puts("-1"); continue;
        }
        while(l < r) {
            int mid = (l + r) >> 1;
            if(f[s][mid - p[s]] >= d) r = mid;
            else l = mid + 1;
        }
        printf("%d\n", q - r);
    }
    return 0;
}
posted @ 2019-10-22 22:23  DMoRanSky  阅读(182)  评论(0编辑  收藏  举报