poj3411

本文介绍了POJ3411这道题目,讨论了如何解决给定n个城市和m条路径的最短费用问题。关键在于理解路径费用规则,并考虑特殊情况,如边可以被多次经过,以及如何避免无限循环。通过深搜结合最优化剪枝和可行性剪枝,标记顶点访问次数以判断循环,并利用邻接表处理多条边的情况。同时,文章提到了几个需要注意的细节,例如到达目标城市后不需要返回,以及n=1时的特例处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目有难度,首先是题意的理解,其次是容易漏掉一些特殊情况,深搜+最优化剪枝+可行性剪枝

大致题意为:给定n个城市,以及m条路径,路径描述如下:ai, bi, ci, Pi, Ri

其中ai表示起点城市,bi表示终点城市,路径是收费的,费用的选取有如下规则:

若在经过该路径之前(包含该路径的两个城市,即算作已经经过),若存在某个城市为ci,则该条路径的费用为Pi,否则为Ri。现在要从城市1到城市n,问需要的最少费用是多少?

本题一个非常容易错误的想法是认为:任意一条路径不能经过两次,即最多经过一次。其实得出这个结论有一定道理:从环的角度来说,如果不断在某个环上循环,那么权值之和只会增加。但是不能因为这个特殊情况就否认某条边可以经过多余一次的事实。由于本题边的权值比较特殊,如果是一般情况权值已经确定,不存在不定因素,那么的确每条边只能经过一次。现在由于边权值的选择有上述两种情况,故有可能经过一条边多次,然后取得最小值的情况(不难想象)。

但是如果不对边的访加以限制,那么就会出现无限循环的情况,所以有没有什么方法可以标记循环情况,若为循环情况则退出,答案是肯定的。由于两个点之间的边可能有多条,故标记边是不容易的,这里可以采取标记点访问的次数来标识是否进入循环,。

这里引入一个“渠限制”概论,即:由于优化的关系,在最多有m条边的情况下,任意一个点访问次数最多为m/2次,否则进入了循环圈,要退出(除开m=1的情况)

这样就可以通过计数顶点访问次数进行可行性剪枝,去除哪些可能进入循环圈的情况。

另外要注意的是:

1)不可能到达了n城市,然后又去其它城市返回到n城市这样的路径绝对比第一次到达n城市的路径权值要大,即到达n城市即可比较权值大小,然后退回到上一层

2)由于可能在两个顶点之间存在多条边的情况,故需要使用邻接表,不能使用邻接矩阵(至少还没有想出来如何表示上述多条边的情况)

3)注意n=1的情况,结果恒为0

4)注意程序里,由于m的最大值为10,故可以直接使用10/2=5作为访问次数上限

下面是代码:156K+0MS

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <cmath>
#include <cstring>
#include <stack>
#include <queue>
#include <map>
#include <algorithm>
#define Lson(p) (p<<1)
#define Rson(p) (p<<1|1)
#define Min(a,b) ((a+b)>>1)
#define lowbit(x) (x&(-x))
#define PI acos(-1.0)
#define Max(a,b) (a)>(b)?(a):(b)
#define Minn(a,b) (a)<(b)?(a):(b)
#define Inf 100000010
#define Maxx 20
using namespace std;
typedef struct Point{ //表节点,记录边的信息
	int index; //终点
	int pcity; //要经过的城市
	int pp,rr; //分别为两种权值
	struct Point *next;
}point,*ppoint;
struct Node{ //表头节点
	ppoint first; //第一条边的指针
}node[Maxx]; //分别为从第i个城市出发的边
int cal[Maxx]; //计数访问次数
int ans,n,m; //分别为结果,城市个数,路径个数
int a,b,c,p,r; //路径的描述信息
void add_edge(){ //创建邻接表
	for(int i=1;i<=n;i++)//初始化为全NULL,头插法插入表节点
		node[i].first=NULL;
	for(int i=0;i<m;i++){ //创建表节点,头插法插入
		scanf("%d%d%d%d%d",&a,&b,&c,&p,&r);
		ppoint temp=(ppoint)malloc(sizeof(point));
		temp->index=b,temp->pcity=c,temp->pp=p,temp->rr=r;
		temp->next=node[a].first;
		node[a].first=temp;
	}
}
void dfs(int ind,int Sum){ //深搜求最小权值之和
	if(Sum>=ans) //最优化剪枝
		return ;
	if(ind==n){ //若为城市n,则直接赋值,由于前面已经判段ans>Sum了
		ans=Sum;
		return ;
	}
	ppoint temp=node[ind].first; 
	while(temp){ //遍历深搜
		if(cal[temp->index]<=4){ //若已经访问的次数小于等于4次,则还有可能继续访问
			cal[temp->index]++;//要将该路径上的两个顶点也计算在已经访问的城市中,然后再判断c的情况
			if(cal[temp->pcity]) //若已经访问了,则取小的权值
				dfs(temp->index,Sum+temp->pp);
			else //否则取大的权值
				dfs(temp->index,Sum+temp->rr);
			cal[temp->index]--;//回溯
		}
		temp=temp->next; //下一个边节点
	}
}	
int main(){
	scanf("%d%d",&n,&m);
	add_edge(); //创建邻接表
	memset(cal,0,sizeof(cal)); //初始化为全没有访问
	ans=Inf; 
	cal[1]=1; //初始化为1,即最开始访问顶点1
	dfs(1,0); //从1开始搜索
	if(ans==Inf) //若从1到达不了n,则输出“impossible”
		printf("impossible\n");
	else //否则输出最小权值
		printf("%d\n",ans);
	return 0;
}


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值