题目有难度,首先是题意的理解,其次是容易漏掉一些特殊情况,深搜+最优化剪枝+可行性剪枝
大致题意为:给定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;
}