POJ3662 最短路+动态规划(分层图最短路)

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 是否可达
}

需要注意的状态的初始化和条件的控制。

将该题推广一下,可以变成求剩下所有的边权的总和的最小值,除了状态转移方程有所变化以外,做法几乎一样。该模型被称为 分层图最短路。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值