「LibreOJ NOIP Round #1」旅游路线 (DP)

本文介绍了一种结合最短路径算法与费用优化的方法,通过预处理费用与使用DP技术来解决特定问题:即在给定城市间有向边及加油成本的情况下,如何在满足最小行程距离要求的同时最大化剩余资金。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接 n个城市之间有m条有向边,在第i个点处可以花费pi将油量变成ci,每走过一条道路,油量将减少1。给出q次查询,每给定初始的城市s和拥有的金钱p,问在经过路程不小于d的情况下,最多能剩下多少钱。

必然要预处理一些关于距离或者费用的东西,由于距离的范围太大,考虑预处理费用。

考虑dp[s][v]表示从s出发,花费v元所能走的最长距离,在每次查询的时候只需要在dp[s]中二分地查找即可。

状态转移方程为dp[s][v] = max(dp[t][v - p[i]] + dis[s][t]),其中dis[s][t]表示在s加油以后从s到t最长的距离。

考虑计算dis[s][t],用dist[s][t][h]表示从s到t走不超过h条路的最远距离,可以直接Floyd求出答案,但是这个数据范围会TLE。可以用倍增的方式预处理出dist[s][t][k],从s到t走不超过2的k次方条路的最远距离,然后就对于每个起点合并出dis[s][t]。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const int maxn = 105;
const ll mod = 1000000007;

int s, t, d;
int n, m, c, q, a, b;
ll maze[maxn][maxn], x;
ll w[maxn], v[maxn], tmp[maxn][maxn][22];
ll dis[maxn][maxn], dp[maxn][maxn*maxn];

void init()
{
    memset(maze,-1,sizeof(maze));
    for(int i = 1;i <= n;i++)
    {
        for(int j = 1;j <= n;j++)
        {
            for(int k = 1;k <= n;k++)
                tmp[i][j][k] = -INF;
            dis[i][j] = -INF;
            tmp[i][j][0] = -INF;
        }
        dis[i][i] = 0;
        tmp[i][i][0] = 0;
    }
}

int main()
{
    scanf("%d%d%d%d", &n, &m, &c, &q);
    for(int i = 1;i <= n;i++)
        scanf("%I64d%I64d", &w[i], &v[i]);
    init();
    for(int i = 1;i <= m;i++)
    {
        scanf("%d%d%I64d", &a, &b, &x);
        maze[a][b] = max(x, maze[a][b]);
    }
    for(int i = 1;i <= n;i++)
    {
        for(int j = 1;j <= n;j++)
            if(maze[i][j] != -1) tmp[i][j][0] = maze[i][j];
    }
    for(int k = 1;k <= 18;k++)
    {
        for(int i = 1;i <= n;i++)
        {
            for(int j = 1;j <= n;j++)
            {
                for(int p = 1;p <= n;p++)
                    tmp[i][j][k] = max(tmp[i][j][k], tmp[i][p][k - 1] + tmp[p][j][k - 1]);
            }
        }
    }
    for(int i = 1;i <= n;i++)
    {
        ll r = min(1LL*c, v[i]);
        for(int k = 18;k >= 0;k--)
        {
            if(!(r & (1LL<<k))) continue;
            ll res[maxn];
            for(int j = 1;j <= n;j++) res[j] = -INF;
            for(int j = 1;j <= n;j++)
            {
                for(int p = 1;p <= n;p++)
                    res[j] = max(res[j], dis[i][p] + tmp[p][j][k]);
            }
            for(int j = 1;j <= n;j++)
                dis[i][j] = res[j];
        }
    }
    for(int j = 0;j <= n*n;j++)
    {
        for(int i = 1;i <= n;i++)
        {
            dp[i][j] = 0;
            if(j < w[i]) continue;
            for(int k = 1;k <= n;k++)
                dp[i][j] = max(dp[i][j], dp[k][j - w[i]] + dis[i][k]);
        }
    }
    for(int i = 1;i <= q;i++)
    {
        scanf("%d%d%d", &s, &t, &d);
        int tp = lower_bound(dp[s] + 1, dp[s] + n*n + 1, d) - dp[s];
        if(tp <= t) printf("%d\n", t - tp);
        else printf("-1\n");
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值