pta 达诺达诺

小A的公司新开发了一款名为“达诺达诺”的新游戏, 小R作为内测玩家抢先体验了这个游戏。游戏规则是在一张地图中,有n个地点(编号从1到n),m条双向通道,每条通道上面都有无限个物品,物品分为奖品与惩罚道具两种, 玩家从1号点出发每经过一个通道都要消耗1点体力,经过通道时会被强制拾取一个通道上的物品, 拾取奖品时会获得相应分数, 拾取惩罚道具时会丢掉相应分数。现在小R想要知道他恰好消耗m点体力时可以获得的最高分数。

注意, 小R可以重复经过一条双向通道。

输入格式:

第一行输入两个整数N(N≤103)、M(M≤2×103), 表示地点数与通道数。

接下来M行, 每行三个整数a、b、c(∣c∣≤104)表示a到b有一条双向通道, 并且有一个分数为c的物品,c为正数时就是奖品,c为负数时就是惩罚道具.

接下来一行有一个整数Q(1≤Q≤105), 代表小R询问的次数

接下来Q行, 每行一个整数m(1≤m≤105), 表示小R想要知道他恰好消耗m点体力时可以获得的最高分数。

输出格式:

输出Q行, 每行一个整数表示最高分数.

输入样例:

6 5
1 2 2
2 3 3
2 4 -4
4 5 4
5 6 10
3
2
3
4

输出样例:

5
8
12

未命名绘图.png

我们可以使用动态规划解决这道题,我们可以发现每一个节点在第m步的值由它的邻居节点在第m-1步的值决定,定义一个二维数组dp[step+1][n+1],状态转移方程为dp[step][v]=max(dp[step][v],dp[step-1][u]+w),v是当前节点,u是它的邻居,w是uv路径上的权值。

时间复杂度:动态规划计算每个节点在不同步数下的最大权值。外层循环执行 mx 次,内层循环对于每个节点的所有邻居执行一次,时间复杂度为 O(mx * n * deg),其中 deg 表示节点的平均出度,mx表示步数的最大值。对于每个查询步数,遍历所有节点以找到最大权值,时间复杂度为 O(k * n)。综合考虑,总体时间复杂度为 O(mx * n * deg + k * n)。

空间复杂度:使用了一个大小为 (mx+1) * (n+1) 的二维数组 ans 来存储每个节点在每一步的最大值,因此空间复杂度为 O(mx * n)。

#include<bits/stdc++.h>
using namespace std;
int main(){
    std::ios::sync_with_stdio(false);std::cin.tie(NULL);
    int n,m;
    cin>>n>>m;
    vector<vector<pair<int,int>>>E(n+1);
    for(int i=0,u,v,w;i<m;i++){
        cin>>u>>v>>w;
        E[u].push_back({v,w});
        E[v].push_back({u,w});
    }
    int k,mx=0;
    cin>>k;
    vector<int>step(k);
    for(int i=0;i<k;i++){
        cin>>step[i];
        mx=max(mx,step[i]);
    }
    vector<vector<long long>>ans(mx+1,vector<long long>(n+1,INT_MIN));
    ans[0][1]=0;
    for(int i=1;i<=mx;i++){
        for(int j=1;j<=n;j++){
            for(const auto& e:E[j]){
                int v=e.first,w=e.second;
                ans[i][j]=max(ans[i][j],ans[i-1][v]+w);
            }
        }
    }
    for(int i=0;i<k;i++){
        long long ret=INT_MIN;
        for(int j=1;j<=n;j++){
            ret=max(ret,ans[step[i]][j]);
        }
        cout<<ret<<endl;
    }
    return 0;
}

我们可以发现每一步的状态只由前一步的状态决定,所以我们可以使用两个一维数组predp,dp存储前一个状态和后一个状态。时间复杂度不变。

空间复杂度:从O(mx*n) -> O(n)。

#include<bits/stdc++.h>
using namespace std;
const int inf=-1000000000;
int main(){
    std::ios::sync_with_stdio(false);std::cin.tie(NULL);
    int n,m,maxstep=0;
    cin>>n>>m;
    vector<vector<pair<int,int>>>E(n+1);
    vector<int>predp,dp(n+1,inf);
    dp[1]=0;
    for(int i=0,u,v,w;i<m;i++){
        cin>>u>>v>>w;
        E[u].push_back({v,w});
        E[v].push_back({u,w});
    }
    int k;
    cin>>k;
    vector<int>step(k);
    for(int i=0;i<k;i++){
        cin>>step[i];
        maxstep=max(maxstep,step[i]);
    }
    vector<int>ans(maxstep+1,inf);
    for(int i=1;i<=maxstep;i++){
        predp=dp;
        for(int j=1;j<=n;j++){
            dp[j]=inf;
            for(auto& e:E[j]){
                int v=e.first,w=e.second;
                if(predp[v]==inf){
                    continue;
                }
                dp[j]=max(dp[j],predp[v]+w);
                ans[i]=max(ans[i],dp[j]);
            }
        }
    }
    for(int i=0;i<k;i++){
        cout<<ans[step[i]]<<endl;
    }
    return 0;
}

下面的代码添加了previs和vis数组,代表前一轮是否被遍历过,和当前轮是否被遍历,运行时间显著地减少了。

#include<bits/stdc++.h>
using namespace std;
int main(){
    std::ios::sync_with_stdio(false);std::cin.tie(NULL);
    int n,m,maxstep=0;
    cin>>n>>m;
    vector<vector<pair<int,int>>>E(n+1);
    vector<int>previs,vis(n+1,0);
    vector<int>predp,dp(n+1,0);
    vis[1]=1;
    for(int i=0,u,v,w;i<m;i++){
        cin>>u>>v>>w;
        E[u].push_back({v,w});
        E[v].push_back({u,w});
    }
    int k;
    cin>>k;
    vector<int>step(k);
    for(int i=0;i<k;i++){
        cin>>step[i];
        maxstep=max(maxstep,step[i]);
    }
    vector<int>ans(maxstep+1,INT_MIN);
    for(int i=1;i<=maxstep;i++){
        predp=dp;
        previs=vis;
        for(int j=1;j<=n;j++){
            dp[j]=INT_MIN;
            vis[j]=0;
            for(auto& e:E[j]){
                int v=e.first,w=e.second;
                if(previs[v]){
                    vis[j]=1;
                    dp[j]=max(dp[j],predp[v]+w);
                    ans[i]=max(ans[i],dp[j]);
                }
            }
        }
    }
    for(int i=0;i<k;i++){
        cout<<ans[step[i]]<<endl;
    }
    return 0;
}

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值