POJ3662 Telephone Lines
该题要在一张给定的图上,从 1 到 N 找到一条路径,该路径上所有的边权里面,可以选择不超过K个边权,免除这些边权的花费,只需要花费剩下的边权里面的最大边权的值,即可以完成 1 到 N 的连接。
我们可以定义一个数组 dp[ i ][ j ],表示从 1 到 i ,已经选择了 j 条边被免除时,1 到 i 剩下的边里面最大的边权。从中我们可以看出,这道题具有动态规划特征。对于每一条边,都具有被选择和不被选择两种情况。所以,状态转移方程为:
dp[ i ][ j ]=max(value[ k ][ i ],dp[ k ][ j ]) (如果不选择 k 到 i 这条边免除);
dp[ i ][ j ]=dp[ k ][ j-1 ] (如果选择 k 到 i 这条边免除);
简单地解释为:如果不选择 k 点到 i 点这条边免除,那么dp[i ][ j ]就为这条边的边权和上一个转态的值(即已有的边数的最大边权)里面更大的一个。如果选择这条边免除,则可以直接确定之前的最大边权仍是下一个状态的最大边权,但是被免除的边数要加一。
但是,该题的动态规划不同之处在于它并不是从头到尾的一个过程,而是可能存在几个状态反复被更新,就像寻找最短路一样,当前状态不一定就是最终的最优情况。即不具有无后效性。
解决该矛盾的方法就是,利用如同 Dijkstra 和 SPFA 等方法类似的思路,将状态放入队列,进行反复更新,从而不断的转移状态,直到最终状态。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int> P;
struct imf
{
int to,val;
};
struct st
{
P a; //P用来记录当前状态的dp[i][j]
int h;
};
vector<imf>Q[1005];
queue<st>G;
int dp[1005][1005];
int main()
{
int N,P,K;
scanf("%d %d %d",&N,&P,&K);
for(int i=1;i<=P;i++)
{
int A,B,w;
scanf("%d %d %d",&A,&B,&w);
Q[A].push_back((imf){B,w});
Q[B].push_back((imf){A,w});
}
memset(dp,0x3f,sizeof(dp));
int INF=dp[1][0];
G.push((st){make_pair(1,0),0}); //注意初始化
dp[1][0]=0;
while(!G.empty())
{
st tmp=G.front();
G.pop();
if(tmp.h>dp[tmp.a.first][tmp.a.second])continue;
for(int i=0;i<Q[tmp.a.first].size();i++)
{
imf e=Q[tmp.a.first][i];
if(dp[e.to][tmp.a.second+1]>tmp.h&&tmp.a.second<=K) // 保证 j <=K
{
G.push((st){make_pair(e.to,tmp.a.second+1),tmp.h});
dp[e.to][tmp.a.second+1]=tmp.h;
}
if(dp[e.to][tmp.a.second]>max(tmp.h,e.val))
{
dp[e.to][tmp.a.second]=max(tmp.h,e.val);
G.push((st){make_pair(e.to,tmp.a.second),dp[e.to][tmp.a.second]});
}
}
}
cout<<(dp[N][K]==INF?-1:dp[N][K]); //判断一下 N 是否可达
}
需要注意的状态的初始化和条件的控制。
将该题推广一下,可以变成求剩下所有的边权的总和的最小值,除了状态转移方程有所变化以外,做法几乎一样。该模型被称为 分层图最短路。