/// *** /// (隔离中的星星)
这几天写了不少关于Dijkstra的题目,而且都是有趣的变形题,所以拿来总结总结。
Dijkstra变形题:
1.
回家的路 (home)
时间限制: 1.000 Sec 内存限制: 128 MB
提交 状态
题目描述
夏之国可以抽象成一个 n 个结点、 m条边的无向连通图(结点的编号为 1 至 n)。图中每条边有它的长度。
图中的 n 号结点是 A 市,是夏之国许多人的父母之乡。每年春节,这些人将从全国各地,通过图中的边回到 A 市。
图中的某些结点上有旅游景点。夏之国共有 k 个旅游景点,景点有不同的级别。我们用一个整数 ai 来表示第 i 个旅游景点的级别。
一个人为了参观一个级别为 x 的景点,愿意比原来多走不超过 x 个单位长度的路。形式化地说,设一个人所在结点为u ,结点 u 到结点 n 的最短路为d0 ,结点 u 到结点 n 且必须经过旅游景点 i 所在结点的最短路为d1 ,那么,这个人能够参观旅游景点 i ,当且仅当 d1≤d0+ai。
你要帮小 Y 求出,对于每个结点 u(1≤u<n),是否存在一个旅游景点 i,使得结点 u 上的人能够参观景点 i。
要注意:
·即使一个人回家途中经过多个景点,也只能参观其中一个,所以景点的级别 不会叠加 。
·为了参观旅游景点,一个人 可以 多次经过一条边。
输入
第一行三个正整数 。
接下来 m 行中,第 i 行是三个正整数ui,vi,li ,表示结点 ui 和结点 vi 之间有一条长度为 li 的边。
接下来 k 行中,第 i 行是两个正整数 vi,ai ,表示第 i 个旅游景点在结点 vi 上,它的级别为 ai。
输出
有 n-1 行,每行一个 0 或一个 1。第 i 行为 1 表示结点 i 上的人能够参观某个景点,为 0 表示结点 i 上的人不会参观景点。
样例输入 Copy
【样例1】 4 5 1 1 4 10 2 1 20 4 2 3 2 3 5 4 3 2 2 7 【样例2】 4 4 2 1 4 1 1 2 9 2 3 2 3 4 6 2 10 3 11
样例输出 Copy
【样例1】 1 1 1 【样例2】 0 1 1
提示
样例1解释:
夏之国可抽象为如图所示的无向连通图,其中结点2上有一个级别为7的景点。
结点1上的人可以走1->4->2->4的路径,长度为16,而最短路长度为10,增加量没有超过所参观景点的级别7。
结点2上的人可以直接在结点2上的景点参观,这显然不会使他所走的路径长度增加。
结点3上的人可以走3->2->4的路径,长度为8,而最短路长度为2,增加量没有超过所参观景点的级别7。
样例2解释:
结点2和结点3上的人,可以直接在出发结点上的景点参观,不会使他们所走的路径长度增加。
结点1上的人若走1->2->3->4,路径长度为17,而最短路的长度为1,他所走路径长度增加了16,但是他所到景点的级别都没有达到16,所以他不会走这条路径。其他能够参观景点的路径也不满足要求。
思路:其实题目大致意思就是找每个点是否有对应的点,使 d1<=d0+ai; 直接死求肯定是不太明智的,而且这样的式子让我们也不太容易下手。我们假设u经过i到达n,那么d1=d[u->i]+d[i->n];简明的说我们求d1即为求 u->i 最短路 和 i->n 最短路。我们发现i->n最短路只与i有关,与u的选择是无关的,而 ai 也只与 i 有关。那么我们就将这两个有关的结合在一起,式子就变形为 (下一行)dddd d[u->i]+ (d[i->n]-ai) <=d[u->n]; 等号左边与u i有关,右边只与u有关。
这样我们就可以建立一个 双层图 。一层用dj跑一遍用来求每个点到n的最短距离,第二层将有ai的i处理为 d=di-ai;这样等式右边就是第一层图,左边就是第二层图以i为起点更新得到的每个点的最短路。用他们变化的距离来更新他们所到的点(注意!!!这时优先队列里不能再从s点开始存储,要直接存储变化的i...看代码就懂了)。
代码:讲了那么多感觉也没啥用,不如直接上代码:
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int>PII;
int n,m,k;
struct EDGE{
int u,v,l;
EDGE* ne;
}edge[1000010];
struct NODE{
EDGE* fir;
}node[1000010];
int tot;
void my_build(int u,int v,int l){
edge[tot].u=u;
edge[tot].v=v;
edge[tot].l=l;
edge[tot].ne=node[u].fir;
node[u].fir=&edge[tot];
tot++;
}
int d[1000010],ds[1000010];
void dj(int s){
priority_queue<PII>q;
PII t;
d[s]=0;
q.push({0,s});
while(!q.empty()){
t=q.top();q.pop();
if(ds[t.second])continue;
ds[t.second]=1;
EDGE* i=node[t.second].fir;
while(i!=NULL){
if(d[i->v]>-t.first+i->l){
d[i->v]=-t.first+i->l;
q.push({-d[i->v],i->v});
}
i=i->ne;
}
}
}
int main()
{
cin>>n>>m>>k;
int u,v,l;
for(int i=1;i<=m;i++){
cin>>u>>v>>l;
my_build(u,v,l);
my_build(v,u,l);
my_build(u+n,v+n,l);//注意双层图的建立方式,可以积累一下
my_build(v+n,u+n,l);
}
memset(d,0x3f,sizeof d);
dj(n);
int a;
priority_queue<PII>que;
PII t;
for(int i=1;i<=k;i++){
cin>>v>>a;
d[v+n]=d[v]-a;
que.push({-d[v+n],v+n});//这很重要,以这些点为开头,而不是以s;
}
while(!que.empty()){
t=que.top();que.pop();
if(ds[t.second])continue;
ds[t.second]=1;
EDGE* i=node[t.second].fir;
while(i!=NULL){
if(d[i->v]>-t.first+i->l){
d[i->v]=-t.first+i->l;
que.push({-d[i->v],i->v});
}
i=i->ne;
}
}
for(int i=1;i<n;i++){
if(d[i]>=d[i+n]){
cout<<1<<endl;
}else{
cout<<0<<endl;
}
}
return 0;
}
point:1.注意一下双层图的建立方式,下次可以效仿学习。
2.注意更新每个点最短路的方式!不是从s点开始,而是从变化的点开始。这个一开始没注 意!
带权Dijkstra例题
分析:带权的Dj,其实最重要的就是搞清楚优先级。距离优先还是其他特定条件优先。如果如果优先级最大的东西比较下来不一样就直接更新;如果比较下来一样就要继续向下搜寻优先级第二大的东西的大小关系。
1.
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
题目描述
GGG 国有 nnn 个城市,城市之间由 mmm 条双向直达铁路连接,一条铁路连接了两个不同的城市,并且乘坐第 iii 条铁路需要wiw_iwi 个单位时间。如果两个城市之间没有直达的铁路,那么则必须要通过其他城市换乘才能相互可达。换乘不需要花费时间,但是 lbromine 非常不喜欢换乘。现在 lbromine 想要从 111 号城市到达 nnn 号城市,他想知道他最少需要经过多少个城市(包括城市 111 和城市 nnn ),以及在保证经过城市最少的情况下需要花费的最少时间。
输入描述:
第一行两个正整数 n,mn,mn,m 表示 GGG国有 nnn 个城市和 mmm 条铁路。 接下来 mmm 行,每行三个整数 ui,vi,wiu_i,v_i,w_iui,vi,wi 表示 lbromine 可以花费 wiw_iwi 的时间在 uiu_iui 和 viv_ivi 之间旅行。 数据保证 111 号城市和 nnn 号城市联通。
输出描述:
输出一行两个整数,第一个整数表示lbromine最少需要经过多少个城市,第二个整数表示保证经过城市最少的情况下需要花费的最少时间,两个整数之间用空格隔开。
示例1
输入
复制4 4 1 2 1 2 3 1 3 4 1 1 4 5
4 4 1 2 1 2 3 1 3 4 1 1 4 5
输出
复制2 5
2 5
备注:
对于 30%30\%30% 的数据,1≤n≤10 , n−1≤m≤201\leq n\leq 10\ ,\ n-1\leq m\leq 201≤n≤10 , n−1≤m≤20 对于 60%60\%60% 的数据,1≤n≤1000 , n−1≤m≤200001\leq n\leq 1000\ ,\ n-1\leq m\leq 200001≤n≤1000 , n−1≤m≤20000 对于 100%100\%100% 的数据,1≤n≤100000 , n−1≤m≤200000, 1≤wi≤100001\leq n\leq 100000\ ,\ n-1\leq m\leq 200000 ,\ 1\leq w_i\leq 100001≤n≤100000 , n−1≤m≤200000, 1≤wi≤10000
理解:这是一题很明显的带权Dj的题目。注意,这题的优先级顺序是 经过城市数量>走路时间。所以我们写代码的时候要优先比较经过城市数量相不相同。如果相同则再比较走路时间。
下面来看看代码吧~******(星星)
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int>PII;
int n,m;
struct EDGE{
int u,v,l;
EDGE* ne;
}edge[1000010];
struct NODE{
EDGE* fir;
}node[1000010];
int tot;
void my_build(int u,int v,int l){
edge[tot].u=u;
edge[tot].v=v;
edge[tot].l=l;
edge[tot].ne=node[u].fir;
node[u].fir=&edge[tot];
tot++;
}
int tim[100010],d[10000010],ds[10000010];
void dj(int s){
memset(d,0x3f,sizeof d);
memset(tim,0x3f,sizeof tim);
PII t;
priority_queue<PII>q;
q.push({0,s});
d[s]=0;
tim[s]=0;
while(!q.empty()){
t=q.top();q.pop();
if(ds[t.second])continue;
ds[t.second]=1;
EDGE* i=node[t.second].fir;
while(i!=NULL){
if(tim[i->v]>tim[t.second]+1){//***
tim[i->v]=tim[t.second]+1;
d[i->v]=d[t.second]+i->l;
q.push({-tim[i->v],i->v});
}else if(tim[i->v]==tim[t.second]+1){//***
if(d[i->v]>d[t.second]+i->l){
d[i->v]=d[t.second]+i->l;
q.push({-tim[i->v],i->v});
}
}
i=i->ne;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
int u,v,l;
for(int i=1;i<=m;i++){
cin>>u>>v>>l;
my_build(u,v,l);
my_build(v,u,l);
}
dj(1);
cout<<tim[n]+1<<" "<<d[n];
return 0;
}
这种待条件的dj与一般dj最主要的区别大概就是在遍历点的出边的时候要有两个if。(见代码中标*的地方)其他地方区别一般,根据具体题目来定。
2.
7-35 城市间紧急救援
分数 25
作者 陈越
单位 浙江大学
作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。
输入格式:
输入第一行给出 4 个正整数 n、m、s、d,其中 n(2≤n≤500)是城市的个数,顺便假设城市的编号为 0 ~ (n−1);m 是快速道路的条数;s 是出发地的城市编号;d是目的地的城市编号。
第二行给出 n 个正整数,其中第 i 个数是第 i 个城市的救援队的数目,数字间以空格分隔。随后的 m 行中,每行给出一条快速道路的信息,分别是:城市 1、城市 2、快速道路的长度,中间用空格分开,数字均为整数且不超过 500。输入保证救援可行且最优解唯一。
输出格式:
第一行输出最短路径的条数和能够召集的最多的救援队数量。第二行输出从 s 到 d 的路径中经过的城市编号。数字间以空格分隔,输出结尾不能有多余空格。
输入样例:
4 5 0 3
20 30 40 10
0 1 1
1 3 2
0 3 3
0 2 2
2 3 2
输出样例:
2 60
0 1 3
这题其实和上面一题没有太大区别,要注意的点就是要标记每个城市最短路的条数,每个城市之前经过的那个城市,以及得到救援队的最大值。注意本题优先级为:最短路>救援队数目。做的时候要当心。
这么在做dj的时候标记last点(也就是标记路径以回溯)可以在这题掌握一下。
看代码:(****呆滞的星星)
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
int n,m,s,e;
int last[1000010];
ll sum[1000010],num[1000010],w[1000010];
struct EDGE{
int u,v;
ll l;
EDGE* ne;
}edge[1000010];
struct NODE{
EDGE* fir;
}node[1000010];
int tot;
void my_build(int u,int v,int l){
edge[tot].u=u;
edge[tot].v=v;
edge[tot].l=l;
edge[tot].ne=node[u].fir;
node[u].fir=&edge[tot];
tot++;
}
int ds[1000010];
ll d[1000010];
void dj(int s){
memset(d,0x3f,sizeof d);
priority_queue<PII>q;
PII t;
d[s]=0,sum[s]=w[s],num[s]=1;
q.push({0,s});
while(!q.empty()){
t=q.top();q.pop();
if(ds[t.second])continue;
ds[t.second]=1;
EDGE* i=node[t.second].fir;
while(i!=NULL){
if(d[i->v]>d[t.second]+i->l){
sum[i->v]=w[i->v]+sum[t.second];
last[i->v]=t.second;
num[i->v]=num[t.second];
d[i->v]=d[t.second]+i->l;
q.push({-d[i->v],i->v});
}else if(d[i->v]==d[t.second]+i->l){
num[i->v]+=num[t.second];
if(sum[i->v]<w[i->v]+sum[t.second]){
sum[i->v]=w[i->v]+sum[t.second];
last[i->v]=t.second;
q.push({-d[i->v],i->v});
}
}
i=i->ne;
}
}
}
void my_print(int u){
if(u==s){
cout<<u<<" ";
return ;
}
my_print(last[u]);
if(u==e){
cout<<u;
}else{
cout<<u<<" ";
}
}
int main()
{
cin>>n>>m>>s>>e;
for(int i=0;i<n;i++){
cin>>w[i];
}
int u,v;
ll l;
for(int i=1;i<=m;i++){
cin>>u>>v>>l;
my_build(u,v,l);
my_build(v,u,l);
}
dj(s);
cout<<num[e]<<" "<<sum[e]<<endl;
my_print(e);
return 0;
}
以上是我目前的一些汇总,以后有关dj的题目还会在此更新的!(不告别)