小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
我们可以使用动态规划解决这道题,我们可以发现每一个节点在第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;
}