最短路综合
前言:
最短路扩展例题。
例题:
奇怪建图的最短路:
在一个国家有两个group,记做1和2,N个city,每个city属于1或者2。每两个city间有一定的距离,现在要从city1去city2,问最短的距离是多少,要求至多只有一次穿越时跨过分属不同group的city。city1总是属于group1,city2总属于group2。
分析:同一城市内建双向边,city1到city2建单向边,注意无解。
特殊最短路计数:
有向图,求每条边被多少条最短路经过
分析:定理:任意一条最短路的某个子路径都是最短路(懒得证,脑补吧)
枚举起点,先跑一遍dj(珍爱生命,远离spfa),把所有属于最短路的边标记,形成最短路图,必定是有向无环图。
考虑到每一条边的贡献为两端端点的最短路条数相乘;
拓扑排序,先求cnt1[ i ]表示从起点到 i 节点的最短路条数,易得如果cnt1[ st ]=1且存在边从u到v 那么cnt1[ v ] += cnt1[ u ];
再求从cnt2[ i ]表示以 i 为起点的最短路条数,cnt2[ i ]初始值为1,若存在边从u到v,则cnt2[ u ]+=cnt2[ v ];
每条边每次贡献为cnt1[ u ] * cnt2[ v ];
给定一张带权图,求从s到t的恰好经过k条边的最短路径长度
分析:用一个矩阵表示初始经过一条边a_ij的长度,没有边是正无穷
矩阵乘法:a矩阵表示经过n条边,b矩阵表示经过m条边,a*b矩阵则表示经过n+m条边,采用了floyd的思想
矩阵快速幂即可
证明:不会,但是会甩链接《矩阵乘法在信息学的应用》
拓展:求s到t长度为k的方案数:
矩阵a [ d ][ i ][ j ]为从i到j长度为d的方案数;
分层图最短路:
有一种图论模型:在图上可以进行k次特殊决策,每次决策不改变图的结构,只改变当前状态,我们称之为分层图最短路。
有k次免费机会的最短路
分析:我们在存储每一个状态时多存储一个变量f表示当前已经使用了几次免费机会,dis[ i ][ j ]表示到 i 节点用了 j 次机会的最短路长度;
每次决策分两部分:
第一部分:不使用机会,直接莽过去;
第二部分:使用机会,用特殊代价转移状态;
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;
char ch;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') f=0,ch=getchar();
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return f?x:-x;
}
int n,m,k,res=1e15;
int st,ed;
int dis[100010][21];
bool vis[10010][21];
int head[100010],cnt;
struct point
{
int nxt,to,val;
}a[100010];
struct p
{
int now,fre,dis;
bool operator <(const p &a) const
{
return dis>a.dis;
}
};
priority_queue<p> q;
inline void add(int x,int y,int z)
{
a[++cnt].nxt=head[x];
a[cnt].to=y;
a[cnt].val=z;
head[x]=cnt;
}
inline void dj()
{
memset(dis,0x3f,sizeof(dis));
dis[st][0]=0;
q.push((p){st,0,0});
while(!q.empty())
{
int now=q.top().now;
int f=q.top().fre;
q.pop();
if(vis[now][f]) continue;
vis[now][f]=1;
for(int i=head[now];i;i=a[i].nxt)
{
int t=a[i].to;
if(f<k&&dis[t][f+1]>dis[now][f])
{
dis[t][f+1]=dis[now][f];
if(t==ed) res=min(res,dis[t][f+1]);
q.push((p){t,f+1,dis[t][f+1]});
}
if(dis[t][f]>dis[now][f]+a[i].val)
{
dis[t][f]=dis[now][f]+a[i].val;
if(t==ed) res=min(res,dis[t][f]);
q.push((p){t,f,dis[t][f]});
}
}
}
}
signed main()
{
n=read(),m=read(),k=read();
st=read(),ed=read();
for(int x,y,z,i=1;i<=m;++i)
{
x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
}
dj();
printf("%lld
",res);
return 0;
}
一个网格图,走一条边花费为2,在某些点转向花费为1,求最短路;
分析状态:位置,方向。与其他最短路不同的就是方向。
考虑到往回走肯定不优,所以我们只需要记录是横向还是纵向即可。
考虑到n较大,我们只记录有用的节点(转向点和起点终点),将所有有用点记录在结构体中,设每个点的编号为(x-1)*n+y;
1.按编号排序,离散化;
2.按x排序,x相等按y排序,把x相等的相邻两个点之间连上边;
3.按y排序,y相等按x排序,把y相等的相邻两个点之间连上边;
跑dj(珍爱生命,远离spfa)
(数据太水没有无法到达的情况,实际上注意判断,虽然我没判就过了)
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;
char ch;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') f=0,ch=getchar();
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return f?x:-x;
}
int n,m,res=1e18;
int stx,sty,edx,edy,tot,st,ed;
int dis[200010][3];
bool vis[200010][3];
int head[233333],cnt;
struct node
{
int x,y,id;
}sub[200010];
struct p
{
int dis,ch,now;
bool operator<(const p &a) const
{
return dis>a.dis;
}
};
priority_queue<p> q;
struct point
{
int nxt,to,val;
int ch;
}a[500010];
inline void add(int x,int y,int z,int ch)
{
a[++cnt].nxt=head[x];
a[cnt].to=y;
a[cnt].val=z;
a[cnt].ch=ch;
head[x]=cnt;
}
inline bool cmp(node a,node b)
{
return a.id<b.id;
}
inline bool cmp1(node a,node b)
{
return (a.x==b.x)?a.y<b.y:a.x<b.x;
}
inline bool cmp2(node a,node b)
{
return (a.y==b.y)?a.x<b.x:a.y<b.y;
}
inline void dj()
{
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=2;++i)
{
dis[st][i]=0;
q.push((p){0,i,st});
}
while(!q.empty())
{
int now=q.top().now;
int ch=q.top().ch;
q.pop();
if(vis[now][ch]) continue;
vis[now][ch]=1;
for(int i=head[now];i;i=a[i].nxt)
{
int t=a[i].to;
int f=a[i].ch;
if(dis[t][f]>dis[now][ch]+a[i].val+(f!=ch))
{
dis[t][f]=dis[now][ch]+a[i].val+(f!=ch);
if(t==ed) res=min(res,dis[t][f]);
q.push((p){dis[t][f],f,t});
}
}
}
}
signed main()
{
n=read(),m=read();
for(int x,y,i=1;i<=m;++i)
{
x=read(),y=read();
sub[++tot].x=x;
sub[tot].y=y;
sub[tot].id=(x-1)*n+y;
}
stx=read(),sty=read(),edx=read(),edy=read();
sub[++tot].x=stx;
sub[tot].y=sty;
sub[tot].id=(stx-1)*n+sty;
sub[++tot].x=edx;
sub[tot].y=edy;
sub[tot].id=(edx-1)*n+edy;
sort(sub+1,sub+tot+1,cmp);
for(int i=1;i<=tot;++i)
{
if(sub[i].id==(stx-1)*n+sty)
{
sub[i].id=i,st=i;
}
else if(sub[i].id==(edx-1)*n+edy)
{
sub[i].id=i,ed=i;
}
else sub[i].id=i;
}
sort(sub+1,sub+tot+1,cmp1);
for(int i=1;i<=tot;++i)
{
if(sub[i-1].x==sub[i].x)
{
add(sub[i].id,sub[i-1].id,(sub[i].y-sub[i-1].y)*2,1);
add(sub[i-1].id,sub[i].id,(sub[i].y-sub[i-1].y)*2,1);
}
}
sort(sub+1,sub+tot+1,cmp2);
for(int i=1;i<=tot;++i)
{
if(sub[i-1].y==sub[i].y)
{
add(sub[i].id,sub[i-1].id,(sub[i].x-sub[i-1].x)*2,2);
add(sub[i-1].id,sub[i].id,(sub[i].x-sub[i-1].x)*2,2);
}
}
dj();
//-1
printf("%lld
",res);
return 0;
}
次短路
求严格次短路。
方法1:dis[ i ][ 1 ]为到 i 节点最短路值,dis [ i ][ 2 ]为到 i 节点次短路值;
这个用spfa,dj我每研究出按什么排序比较好
inline void spfa()
{
for(int i=1;i<=n;++i) dis[i][1]=dis[i][2]=1e15;
dis[1][1]=0;
q.push(1);
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
for(int i=head[now];i;i=a[i].nxt)
{
int t=a[i].to;
bool flag=0;
if(dis[t][1]>dis[now][1]+a[i].val)
{
dis[t][2]=dis[t][1];
dis[t][1]=dis[now][1]+a[i].val;
flag=1;
}
if(dis[t][2]>dis[now][1]+a[i].val&&dis[t][1]!=dis[now][1]+a[i].val)
{
dis[t][2]=dis[now][1]+a[i].val,flag=1;
}
if(dis[t][2]>dis[now][2]+a[i].val)
{
dis[t][2]=dis[now][2]+a[i].val;
flag=1;
}
if(flag&&!vis[t])
{
vis[t]=1;
q.push(t);
}
}
}
}
方法2:求出最短路和路径,枚举删边;
方法3:A*,不会;
特殊最短路模型
给定一张无向图, 要求一棵最小生成树。 根结点确定为1,权值的定义为:每个结点都有自己的权值,每条边有长度, 构建每条边的花费是,此边的单价 * 所有后代的结点权重之和。
转而考虑每个节点花费:该节点权值 * 节点到根的距离;
从1号节点跑一遍最短路就行了
妙啊
基础图论大杂烩
BZOJ4144(权限题,自己找吧qwq)
n个点m条边的带权无向图,有s个点是加油站,每辆车有个油量上限c,保证x和y是加油站,能否从x走到y;
分析:我们发现除了加油站,其他点都是垃圾都没有用,所以在加油站之间搞MST,然后货车运输就好了(别告诉我你不会货车运输)
怎么确定边权呢?
我们记 f [ i ]为距离 i 号节点最近的加油站;
考虑如果从x到y的路径上出现一个点k,f[ k ]属于z,那么从x到z,再用z到y一定更优;
把所有加油站都扔进堆里面跑一遍dj,求出所有点的dis和 f ;
枚举每一条边,如果有一条边两端的节点属于两个不同的加油站,那么那么就在待定最小生成树图上增加一条连接u,v的边,权值为dis[u]+dis[v]+len(u,v);