题意:给出一张图,每个节点有一个happy值,给出起点、终点、图的边与权值,求出本图的最短路、最短路的条数、最短路但是happy值最大的路径、此路径的平均happy值 共4个参数!
难点:N的范围是200,所以本题不是什么最短路优化什么的,就是普普通通的最短路。集和了最短路的路径输出、最短路条数、最短路节点数、最短路情况下的二次条件问题(happy值最大),我觉得特别经典。
然鹅!我从下午2点一直做到晚上6点半,一句一句的debug,真是非常可耻以及丢人啊。。。
记录一下我自己犯下的错误吧,╥﹏╥:
1、题目英语看不懂:
You are supposed to find your clients the route with the least cost while gaining the most happiness.
开始陷入沉思:题目是要我求 “最短路情况下的,最大的happy呢” 还是让我求 “最大的happy情况下的,最短路呢” ?我认为是后者,于是浪费了30分钟,觉得用DFS解决这个问题。可是好像和最短路没什么关系。百度一看,哦题目不是这个意思。。。是前者额。。。
2、如何在最短路的松弛时候保存路径,又能保存路径的节点数目呢?路径的保存是用倒序的关系,在整条路径出来之前,是没办法用老办法用递归的方式,遍历路径的,那咋知道当前松弛的情况下,路径节点的个数呢?与此类似的,是要保存当前路径经过的城市的happy值,如何维护松弛过程中的Happy值呢?
看了一眼博客,猛然想起上次做的次短路题目——最短路问题中,关于路径的新特性(变量)的维护,都可以通过增加一个数组,在松弛过程中同步进行变化,表示的物理意义是 “当前松弛过程中,到达城市 i 时已收集到的happy(或 已经过的节点个数)等等”,然后再在松弛过程中相对应操作变化即可 ,也就是说加几个全局变量就行。同时要注意的是—— “路径数” 等参数初始化要考虑要不要特殊化(如起点置1)。
3、这个更过是我自己的问题了: 由于我个人比较懒,所以在学习完毕一个算法之后就会对自己的模板进行尽力的精简,而又使得程序不会出错(可以参见博客的最短路模板)。但是这样有些时候就会出现问题——比如最短路的本题这种加入额外参数在松弛过程中,或者是次短路这种,都需要稍微变化。这些场景下,对于Dijkstra的模板,在松弛阶段有一个要求就是
if(!book[i]&&dis[i]>dis[u]+e[u][i])
对标记过的点,不再重复计算。
最短路算法众所周知,book数组是很重要的标记作用。而这个book数组在dijkstra算法中,不仅仅是在循环内的第一个寻找最近未标记点有用,也不仅仅是在松弛操作中减少重复松弛判定而已,在最短路变形中,如果遗漏,将会起到非常大的程序错误!(血与泪的教训。。。)
因为:纯粹的最短路问题,如果不加这个 !book限定,程序也会无误,仅仅是多跑一遍而已,因为后一个关于路径大小松弛的条件往往会使得本语句依然不会运行!但是,类似本题以及次短路以及更多的最短路变形题目,会加入更多的参数要求,使得在松弛操作中,对于最优解是否有效以及更新的判定,不再仅有路径来决定,还要由很多其他的参数,比如本题的“happy值、经过的路径长度”等等影响更新。这就使得如果 忘记添加 !book[i] 这句话,将会使得最短路径不变的情况下,其他的参数却会 “错误的” 重复累加,进而得出错误的结果!
我的模板中没有添加!book的习惯(偷懒)。所以不管是在这题,还是在次短路题中,我都为这个 !book[i] 欲仙欲死啊。今日写了博客,想必能更有印象吧。今日的错误还是这个book[i]只写到了第一个if里,后面的else if没写,导致其他参数不断累加重复计算,我直接改成 if(book[i]) continue; 就接近AC了。。。
4、第四点也是我太糊涂了。我居然会以为——不知道怎么解释,太乱了,看代码吧——我以为注释的代码与下方的else if 是等价的。。。然鹅并不是。。。
5、输出的递归写错了。简直愚蠢。
6、学习了Codeblocks的debug方法。
7、希望自己能更好的所学与所用吧!(眼睛已瞎)
Code:
代码可能不是很美观,注释部分是错的,我留下来给自己留个念
#include<bits/stdc++.h>
using namespace std;
#define inf 209
#define INF 0x3f3f3f3f
int s,g;//起点终点
int n,m;
int book[inf],e[inf][inf],dis[inf];
int city[inf];//此城市的快乐值
int happy[inf];//到此城市为止共收集最大快乐
int sum[inf];//到此城市为止,经过的城市数目
int route[inf];//路径
int ans[inf];//最短路径条数
map<string,int>mp1;
map<int,string>mp2;
void init()
{
//memset(book,0,sizeof book);
mp1.clear();
mp2.clear();
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
e[i][j]=(i==j)?0:INF;
}
void input()
{
cin>>n>>m;
init();//初始化
string str1,str2;
cin>>str1;//起点
s=0;
mp1[str1]=0;
mp2[0]=str1;
int i;
for(i=1;i<n;i++)
{
cin>>str2>>city[i];
mp1[str2]=i;
mp2[i]=str2;
if(str2=="ROM")g=i;
}
for(i=0;i<m;i++)
{
int value;
cin>>str1>>str2>>value;
int u=mp1[str1];
int v=mp1[str2];
e[u][v]=e[v][u]=value;
}
}
void dijkstra()
{
int i,j,u,mini;
for(i=0;i<n;i++)dis[i]=e[s][i];
ans[s]=1;
for(j=0;j<n;j++)
{
mini=INF;
for(i=0;i<n;i++)
if(!book[i]&&dis[i]<mini)
mini=dis[u=i];
book[u]=1;
for(i=0;i<n;i++)
if(book[i])continue;
else if(dis[i]>dis[u]+e[u][i])//更短的路,直接更新
{
dis[i]=dis[u]+e[u][i];
route[i]=u;//i点的上个点是u
happy[i]=happy[u]+city[i];//到达当前城市的快乐值更新
sum[i]=sum[u]+1;//到达当前城市的数目值更新
ans[i]=ans[u];//最短路径条数更新
}/*
else if(dis[i]==dis[u]+e[u][i]&&happy[i]<happy[u]+city[i]||(happy[i]==happy[u]+city[i]&&sum[i]>sum[u]+1))
{
ans[i]+=ans[u];//唯一的区别以及不用更改路径
route[i]=u;//i点的上个点是u,记录路径
happy[i]=happy[u]+city[i];//到达当前城市的快乐值更新
sum[i]=sum[u]+1;//到达当前城市的数目值更新
ans[i]=ans[u];//最短路径条数更新
}*/
else if(dis[i]==dis[u]+e[u][i])//相同的路
{
ans[i]+=ans[u];//增加条数
if(happy[i]<happy[u]+city[i])//不够快乐
{
happy[i]=happy[u]+city[i];//到达当前城市的快乐值更新
sum[i]=sum[u]+1;//到达当前城市的数目值更新
route[i]=u;//i点的上个点是u
}
else if(happy[i]==happy[u]+city[i]&&sum[i]>sum[u]+1)//城市太多,导致平均值不够大
{
//happy[i]=happy[u]+city[i];//到达当前城市的快乐值更新
sum[i]=sum[u]+1;//到达当前城市的数目值更新
route[i]=u;//i点的上个点是u
}
}
}
}
void output(int i)
{
if(i==s)
cout<<mp2[i];
else
{
output(route[i]);
cout<<"->"<<mp2[i];
}
}
int main()
{
input();
dijkstra();
cout<<ans[g]<<' '<<dis[g]<<' '<<happy[g]<<' '<<(int)(happy[g]/sum[g])<<endl;
output(g);
return 0;
}
Code2:(2019-08-18更新)
注意点:1、起点是需要进行一次松弛选择的。作用是在对起点s的邻接点进行dis_happy与dis_cost与route_num的初始化,如果手动初始化那么route_num就会很麻烦,要抠出邻接点赋值为1是相当不方便的,没有必要这样。所以Dijkstra的大循环次数是N而不是N-1. (以前个人喜欢把起点手动赋值然后不加入到大循环的次数里面,但是本体有很多讨论需要的数组是要对起点附近的邻接点进行专门初始化的,不是简单的memset操作)
2、内循环中,至今遇见的问题不论是哪种最短路变形——次短路也好或是本题的附加条件判断也好,都是在!book下进行条件控制。因为已经是松弛点必然是最优点,以后不需要对它进行更改。所以之后的讨论与更新都是在!book下进行。
#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
#define inf 209
#define INF 0x3f3f3f3f
#define loop(x,y,z) for(x=y;x<z;x++)
int n,m,s,g;
int mp[inf][inf],happy[inf],book[inf];
int dis_cost[inf],dis_happy[inf],node_num[inf],route_num[inf];
int route[inf];
unordered_map<string,int>name2id;
unordered_map<int,string>id2name;
void Dijkstra()
{
int i,j;
loop(i,0,n)dis_cost[i]=INF;
memset(book,0,sizeof book);
memset(dis_happy,0,sizeof dis_happy);
memset(node_num,0,sizeof node_num);
memset(route_num,0,sizeof route_num);
route_num[s]=1;
dis_cost[s]=0;
loop(j,0,n)//n-1轮数
{
int u;
int minCost=INF;
loop(i,0,n)
if(!book[i]&&dis_cost[i]<minCost)
minCost=dis_cost[u=i];
book[u]=1;
loop(i,0,n)
if(!book[i])
{
if(dis_cost[u]+mp[u][i]<dis_cost[i])
{
dis_cost[i]=dis_cost[u]+mp[u][i];
node_num[i]=node_num[u]+1;
route_num[i]=route_num[u];
dis_happy[i]=dis_happy[u]+happy[i];
route[i]=u;
}
else if(dis_cost[u]+mp[u][i]==dis_cost[i])
{
route_num[i]+=route_num[u];
if(dis_happy[u]+happy[i]>dis_happy[i])
{
dis_happy[i]=dis_happy[u]+happy[i];
node_num[i]=node_num[u]+1;
route[i]=u;
}
else if(dis_happy[u]+happy[i]==dis_happy[i]&&node_num[i]>node_num[u]+1)
{
node_num[i]=node_num[u]+1;
route[i]=u;
}
}
}
}
}
void printRoute(int i)
{
if(i==s)
{
double averageG=dis_happy[g]*1.0/node_num[g];
printf("%d %d %d %d\n",route_num[g],dis_cost[g],dis_happy[g],(int)averageG);
cout<<id2name[i];
return;
}
printRoute(route[i]);
cout<<"->"<<id2name[i];
}
int main()
{
int i,j;
string str;
cin>>n>>m;
cin>>str;
s=j=0;
name2id[str]=j;
id2name[j]=str;
loop(j,1,n)
{
cin>>str>>happy[j];
name2id[str]=j;
id2name[j]=str;
if(str=="ROM")g=j;
}
loop(i,0,n)
loop(j,0,n)
mp[i][j]=INF;
loop(i,0,m)
{
string s1,s2;
cin>>s1>>s2>>j;
int x=name2id[s1];
int y=name2id[s2];
mp[x][y]=mp[y][x]=j;
}
Dijkstra();
printRoute(g);
return 0;
}