最短路径附带限制问题
这一节要讨论的是带有额外条件的DP问题。
以下的这个问题是个很好的例子。
无向图G有N个结点,它的边上带有正的权重值。
你从结点1开始走,并且一开始的时候你身上带有M元钱。如果你经过结点i, 那么你就要花掉S[i]元(可以把这想象为收过路费)。如果你没有足够的钱, 就不能从那个结点经过。在这样的限制条件下,找到从结点1到结点N的最短路径。 或者输出该路径不存在。如果存在多条最短路径,那么输出花钱数量最少的那条。 限制:1<N<=100 ; 0<=M<=100 ; 对于每个i,0<=S[i]<=100;正如我们所看到的, 如果没有额外的限制条件(在结点处要收费,费用不足还不给过),那么, 这个问题就和经典的迪杰斯特拉问题一样了(找到两结点间的最短路径)。 在经典的迪杰斯特拉问题中, 我们使用一个一维数组来保存从开始结点到每个结点的最短路径的长度, 即M[i]表示从开始结点到结点i的最短路径的长度。然而在这个问题中, 我们还要保存我们身上剩余多少钱这个信息。因此,很自然的, 我们将一维数组扩展为二维数组。M[i][j]表示从开始结点到结点i的最短路径长度, 且剩余j元。通过这种方式,我们将这个问题规约到原始的路径寻找问题。 在每一步中,对于已经找到的最短路径,我们找到它所能到达的下一个未标记状态(i,j), 将它标记为已访问(之后不再访问这个结点),并且在能到达这个结点的各个最短路径中, 找到加上当前边权重值后最小值对应的路径,即为该结点的最短路径。 (写起来真是绕,建议画个图就会明了很多)。不断重复上面的步骤, 直到所有的结点都访问到为止(这里的访问并不是要求我们要经过它, 比如有个结点收费很高,你没有足够的钱去经过它,但你已经访问过它) 最后Min[N-1][j]中的最小值即是问题的答案(如果有多个最小值, 即有多条最短路径,那么选择j最大的那条路径,即,使你剩余钱数最多的最短路径)
#include<cstdio> #include<algorithm> #include<queue> using namespace std; struct my{ int next,v,w,cost; }; const int maxn=1000; const int nil=0x7f7f7f7f; int tot; my bian[maxn]; int n,m; int adj[maxn]; int dp[maxn][maxn],zhi; bool vis[maxn][maxn]; void myinsert(int u,int v,int w,int cost){ bian[++tot].v=v; bian[tot].next=adj[u]; adj[u]=tot; bian[tot].w=w; bian[tot].cost=cost; } void dij(){ for (int i=0;i<=n;i++) for (int j=0;j<=zhi;j++) dp[i][j]=nil; dp[1][zhi]=0; while(true){ int l,k; int minn=nil; for (int i=1;i<=n;i++) for (int j=1;j<=zhi;j++) if(!vis[i][j]&&dp[i][j]<minn) minn=dp[i][j],l=i,k=j; if(minn==nil) break; vis[l][k]=true; for (int i=adj[l];i;i=bian[i].next){ int v=bian[i].v; if(k-bian[i].cost>=0&&dp[v][k-bian[i].cost]>dp[l][k]+bian[i].w){ dp[v][k-bian[i].cost]=dp[l][k]+bian[i].w; } } } } int main(){ int u,v,w,cost; scanf("%d%d%d",&n,&m,&zhi); for (int i=1;i<=m;i++){ scanf("%d%d%d%d",&u,&v,&w,&cost); myinsert(u,v,w,cost); myinsert(v,u,w,cost); } dij(); int ans=nil; for (int j=1;j<=zhi;j++) if(dp[n][j]<=ans) ans=dp[n][j]; printf("%d\n",ans); return 0; }