1495:【例 2】孤岛营救问题
分层最短路做。以获取钥匙的状态建立分层图,然后BFS就行了
https://blog.csdn.net/a_pathfinder/article/details/100537489 里面写了BFS+状压 和 最短路得解法
like 汽车加油行驶问题(另一个分层图的问题)
要先算出最多边数:1>>10*10*10=1024000;
把原来矩阵每个坐标存成一个点,再对可到邻边赋权值1,在建图的时候,我们只要连接一个双向边即可,假设j是从i左移一格,那么i就是j右移一格。上下同理。
值得注意的是,我们每次在一层的时候,在没有钥匙i的时候,我们需要把i钥匙锁在的点u,连接到有了i钥匙的那一层对应的位置上v,权值是0,这样就建成了1>>种类 的图,
并且图直接有连接。最后跑一遍最短路即可。
ps:要记录总层数的总点数,之后dis[]的初始化时所有点。
//下面是最短路得做法,我还没看 /* 这里涉及到的变化还蛮多的,我们要先算出最多边数:1>>10*10*10=1024000; 把原来矩阵每个坐标存成一个点,再对可到邻边赋权值1,在建图的时候,我们只要连接一个双向边即可,假设j是从i左移一格,那么i就是j右移一格。上下同理。 值得注意的是,我们每次在一层的时候,在没有钥匙i的时候,我们需要把i钥匙锁在的点u,连接到有了i钥匙的那一层对应的位置上v,权值是0,这样就建成了1>>种类 的图, u(锁在的位置)--->v(钥匙的位置),权值为0 并且图直接有连接。最后跑一遍最短路即可。 ps:要记录总层数的总点数,之后dis[]的初始化时所有点。 */ //分层求最短路 #include<bits/stdc++.h> using namespace std; const int N = 12; const int M = 1024100; const int INF = 0x3f3f3f3f; typedef pair<int,int> P; struct keyn{ int x,y; }key[N][20];//key[i][j] 种类为i的钥匙第j把的坐标 int n,m,p,s,k,cnt,layer,nn,nsum;//n宽,m长,p种类,s总钥匙数,k总障碍数,cnt计数器,layer层数,nn每层的点,nsum总点数 //layer = 1<<p; //层数为 2^(钥匙种类数) //nn = n*m; //每一层点数 //nsum = n*m*layer; //总共点数 int num[N][N],fg[200][200]; int head[M],nex[M],ver[M],edge[M]; int hadk[N],kn[N],vis[M],dis[M]; void add(int x,int y,int w){ ver[++cnt] = y; nex[cnt] = head[x]; edge[cnt] = w; head[x] = cnt; } void read(){ cnt = 0; int x,y; scanf("%d%d%d%d",&n,&m,&p,&k); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) num[i][j] = ++cnt; ///把每个左边当成一个点 for(int i=1,g,u,v;i<=k;i++){ scanf("%d%d",&x,&y); u = num[x][y]; //把两个坐标连成一条边 scanf("%d%d",&x,&y); v = num[x][y]; scanf("%d",&g); if(g==0) g=-1; fg[u][v] = fg[v][u] = g; } scanf("%d",&s); for(int i = 1,q;i <= s;i++){ scanf("%d%d%d",&x,&y,&q); kn[q]++; //这种钥匙的数量 key[q][kn[q]].x = x; key[q][kn[q]].y = y; } } void build(){ layer = 1<<p; //层数为 2^(钥匙种类数) nn = n*m; //每一层点数 nsum = n*m*layer; //总共点数 for(int t=0;t<layer;t++){ for(int i=1;i<=p;i++){ if(t&(1<<(i-1))) hadk[i] = 1; else hadk[i] = 0; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ int u = num[i][j],v = num[i][j+1];//向右连边 if(v && fg[u][v]!=-1) if(fg[u][v]==0 || hadk[fg[u][v]]){ //可以走或者是有钥匙 就连边 add(t*nn+u,t*nn+v,1); add(t*nn+v,t*nn+u,1); } v = num[i+1][j]; //向下连边 if(v && fg[u][v]!=-1) if(fg[u][v]==0 || hadk[fg[u][v]]){ //可以走或者是有钥匙 就连边 add(t*nn+u,t*nn+v,1); //层数*每一层点数 add(t*nn+v,t*nn+u,1); } } for(int i=1;i<=p;i++){ if(!hadk[i]) //没有钥匙才可以移动状态 for(int j=1;j<=kn[i];j++){ //如果这一层没有这样的钥匙,那么就把这种所对应的钥匙的地方连线,边权为0 int u = num[key[i][j].x][key[i][j].y]; add(t*nn+u,( t|(1<<(i-1)) ) *nn+u,0); } } } } void dj(){ priority_queue<P> q; for(int i = 0;i <= nsum; i++) dis[i] = INF; q.push(make_pair(0,1)),dis[1] = 0; //first是距离,second是位置 while(q.size()){ int u = q.top().second; q.pop(); if(vis[u]) continue; vis[u] = 1; for(int i = head[u];i;i = nex[i]){ int v = ver[i],w=edge[i]; if(dis[v] > dis[u] + w){ dis[v] = dis[u] + w; q.push(make_pair(-dis[v],v)); } } } } void spfa(){ queue<int> q; for(int i = 0;i <= nsum;i++) dis[i] = INF; q.push(1),dis[1] = 0,vis[1] = 1; while(q.size()){ int u = q.front(); q.pop();vis[u] = 0; for(int i=head[u];i;i = nex[i]){ int v = ver[i],w=edge[i]; if(dis[v] > dis[u] + w){ dis[v] = dis[u] + w; if(!vis[v]){ q.push(v),vis[v] = 1; } } } } } void solve(){ int ans = INF; for(int i =0;i<layer;i++) ans = min(ans,dis[i*nn+num[n][m]]); if(ans==INF) printf("-1 "); else printf("%d ",ans); } int main(){ read(); build(); //spfa(); dj(); solve(); return 0; }
1496:【例 3】架设电话线
这个不是求最短路了,而是需要求出第k+1最短边
分层求最短路
我们把节点扩展到二维,二元组(x,p)代表一个节点,从(x,p)到(y,p)上有一个长为w的边,(x,p)到(y,p+1)上有长度为0的边,最后对所有层求最短路,
分层的时候只能从低层到高层连大家都知道吧,因为你不能说我把一个边变成0后再变回去。
同一层边权是多少就是多少,不同层就是0
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int maxm=2e5+10; const int INF=0x3fffffff; typedef long long LL; /* 第二种:分层求最短路 我们把节点扩展到二维,二元组(x,p)代表一个节点,从(x,p)到(y,p)上有一个长为w的边,(x,p)到(y,p+1)上有长度为0的边,最后对所有层求最短路, 分层的时候只能从低层到高层连大家都知道吧,因为你不能说我把一个边变成0后再变回去。 */ typedef pair<int,int> pp; int n,m,k,cnt; int head[maxm],to[maxm],wei[maxm],next[maxm]; int vis[maxm],dis[maxm]; void add(int x,int y,int z){ to[++cnt]=y; wei[cnt]=z; next[cnt]=head[x]; head[x]=cnt; } void inti(){ scanf("%d %d %d",&n,&m,&k); for(int i=1;i<=m;i++){ int x,y,z; scanf("%d %d %d",&x,&y,&z); add(x,y,z); add(y,x,z); for(int j=1;j<=k;j++){ //同一层滴 add(j*n+x,j*n+y,z);add(j*n+y,j*n+x,z); //不是同一层的 add((j-1)*n+x,j*n+y,0);add((j-1)*n+y,j*n+x,0); } } } void dij1(){ priority_queue<pp,vector<pp>,greater<pp> > q; for(int i=0;i<=maxm;i++) dis[i]=INF; dis[1]=0; q.push(make_pair(0,1)); while(!q.empty()){ int op=q.top().second; //下标 q.pop(); if(vis[op]) continue; vis[op]=1; for(int i=head[op];i;i=next[i]){ int v=to[i]; int w=wei[i]; if(dis[v]>max(dis[op],w)){ //最大值 dis[v]=max(dis[op],w); if(!vis[v]) q.push(make_pair(dis[v],v)); } } } } int main(){ inti(); dij1(); if(dis[k*n+n]==INF) printf("-1 "); else printf("%d ",dis[k*n+n]); return 0; }
1502:汽车加油行驶问题
这道题也可以用分层图解决 +spfa(也就这个好些一点
也可以用普通的bfs+spfa解决(我更喜欢这个wwww
即总共建k+1层图,只有层与层之间有边,汽车每走一步就会向上移动一层。建边规则满足题目要求即可。
因为k是能走的长度,但是我还是不太能理解建边的过程【这个建边好麻烦】
#include<bits/stdc++.h> #define N 200005 using namespace std; int Map[105][105]; int num[105][105][15]; struct ss { int v,next,w; }; ss edg[N*4]; int head[N],now_edge=0; void addedge(int u,int v,int w) { edg[now_edge]=(ss){v,head[u],w}; head[u]=now_edge++; } int dis[N]; int vis[N]={0}; void spfa() { for(int i=0;i<N;i++)dis[i]=INT_MAX/2; dis[num[1][1][0]]=0; //节点(离散后) queue<int>q; q.push(num[1][1][0]); vis[num[1][1][0]]=1; while(!q.empty()) { int now=q.front(); q.pop(); vis[now]=0; for(int i=head[now];i!=-1;i=edg[i].next) { int v=edg[i].v; if(dis[v]>dis[now]+edg[i].w) { dis[v]=dis[now]+edg[i].w; if(!vis[v]) { q.push(v); vis[v]=1; } } } } } int main() { int n,k,a,b,c; memset(head,-1,sizeof(head)); scanf("%d %d %d %d %d",&n,&k,&a,&b,&c); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)scanf("%d",&Map[i][j]); //1为有油库,0为没有 int cnt=1; for(int kk=0;kk<=k;kk++) { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) num[i][j][kk]=cnt++; //转化为节点 } //0--1层 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { if(i+1<=n)addedge(num[i][j][0],num[i+1][j][1],0); if(j+1<=n)addedge(num[i][j][0],num[i][j+1][1],0); if(i-1>=1)addedge(num[i][j][0],num[i-1][j][1],b); if(j-1>=1)addedge(num[i][j][0],num[i][j-1][1],b); } //(1~k-1)--(2~k)层 for(int kk=1;kk<k;kk++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(!Map[i][j]) //没有油库 { if(i+1<=n)addedge(num[i][j][kk],num[i+1][j][kk+1],0); if(j+1<=n)addedge(num[i][j][kk],num[i][j+1][kk+1],0); if(i-1>=1)addedge(num[i][j][kk],num[i-1][j][kk+1],b); if(j-1>=1)addedge(num[i][j][kk],num[i][j-1][kk+1],b); addedge(num[i][j][kk],num[i][j][0],c+a); //没有油库,还需要加上c } else //有油库的话,就只需要费用a并且是从0走到kk层 { addedge(num[i][j][kk],num[i][j][0],a); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(!Map[i][j]) { addedge(num[i][j][k],num[i][j][0],c+a); //没有油库,还需要加上c } else { addedge(num[i][j][k],num[i][j][0],a);//有油库的话,就只需要费用a并且是从0走到kk层 } spfa(); int ans=INT_MAX; for(int i=0;i<=k;i++)ans=min(ans,dis[num[n][n][i]]); //求最小 printf("%d ",ans); return 0; }
洛谷:
P3831 [SHOI2012]回家的路
https://www.luogu.com.cn/problem/P3831
先把题目读懂,换乘表示可以转弯(我一开始没有意识到
洛谷的题解都写得很好
经典的分层图最短路裸题,在此简要介绍:
我们可能遇到这样的图论模型:在一个正常的图上可以进行 kk 次决策,对于每次决策,不影响图的结构,只影响目前的状态或代价。
同时这个图论模型和经典的最短路有关,这样我们可以考虑运用分层图最短路。
此题为朴素的裸题,在此仅介绍一种(时空非最优)的易于理解的实现方法:(我懒得再写一遍了
此题的决策为转向,由于只存在横向和纵向两个放学,我们对这两个方向分别建立一层。即一层只连原图横向边,一层只连纵向边。
对于转向这个决策,将决策前的状态和决策后的状态间连接一条权值为决策代价的边,表示付出该代价转换了状态。
在本题中,即上下两层对应点连接一条权值为1的边,层内边权均为2.
然后跑最短路即可。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=200010; const int maxm=800010; const int INF=0x3f3f3f3f; typedef long long LL; typedef unsigned long long ull; int red(){ int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } int head[maxm],cnt=1; struct node{ int to,nex,wei; }ed[maxm]; void adde(int x,int y,int z){ ed[cnt].to=y; ed[cnt].nex=head[x]; ed[cnt].wei=z; head[x]=cnt++; ed[cnt].to=x; ed[cnt].nex=head[y]; ed[cnt].wei=z; head[y]=cnt++; } struct node1{ int x,y,id; }a[maxn]; //两个比较函数 bool cmp1(node1 a,node1 b){ //根据x来排序 if(a.x==b.x) return a.y<b.y; return a.x<b.x; } bool cmp2(node1 a,node1 b){ //根据y来排序 if(a.y==b.y) return a.x<b.x; return a.y<b.y; } int n,m; queue<int> q; int d[maxn],S,T; bool vis[maxn]; void spfa(){ for(int i=1;i<=2*m+4;i++) d[i]=INF; d[S]=0; vis[S]=1; q.push(S); while(!q.empty()){ int op=q.front(); q.pop(); vis[op]=0; for(int i=head[op];i;i=ed[i].nex){ int t=ed[i].to; if(d[t]>d[op]+ed[i].wei){ d[t]=d[op]+ed[i].wei; if(!vis[t]){ vis[t]=1; q.push(t); } } } } } int main(){ n=red(); m=red(); //s是m+1,t是m+2 S=m+1;T=m+2; for(int i=1;i<=m+2;i++){ a[i].x=red(); a[i].y=red(); a[i].id=i; } sort(a+1,a+m+3,cmp1); //x排的 for(int i=1;i<m+2;i++){ if(a[i].x==a[i+1].x) adde(a[i].id,a[i+1].id,2*(a[i+1].y-a[i].y)); } sort(a+1,a+m+3,cmp2); //y排的 for(int i=1;i<m+2;i++){ //两层之间的节点应该区分,所以都要加上m+2 if(a[i].y==a[i+1].y) adde(a[i].id+2+m,a[i+1].id+m+2,2*(a[i+1].x-a[i].x)); } for(int i=1;i<=m;i++){ //所有的中转站 两层之间连线,权值为1 adde(i,i+2+m,1); } //两层之间的起点与起点,终点与终点连线,权值为0 adde(m+1,2*m+3,0); adde(m+2,2*m+4,0); spfa(); if(d[T]==INF){ printf("-1"); return 0; } printf("%d",d[T]); return 0; } //
P4568 [JLOI2011]飞行路线
https://www.luogu.com.cn/problem/P4568
这道题和一本通上面的第k最短路很像,也是分层图的模板题
//各层内部正常连边,各层之间从上到下连权值为0的边。每向下跑一层,就相当于免费搭一次飞机。跑一遍从s到t+n*k+t的最短路即可
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=110005; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //这道题和一本通上面的第k最短路很像,也是分层图的模板题 //各层内部正常连边,各层之间从上到下连权值为0的边。每向下跑一层,就相当于免费搭一次飞机。跑一遍从s到t+n*k+t的最短路即可 int n,m,k,s,t; struct node{ int to,nex,val; }ed[2500001]; //这个范围是怎么求得 int head[maxn],cnt; int vis[maxn],dis[maxn]; void adde(int x,int y,int z){ ed[++cnt].nex=head[x];ed[cnt].to=y;ed[cnt].val=z; head[x]=cnt; } typedef pair<int,int> pp; void dij(int st){ memset(dis,0x3f,sizeof(dis)); dis[st]=0; priority_queue<pp,vector<pp>,greater<pp> > q; q.push(make_pair(0,st)); while(!q.empty()){ int x=q.top().second; q.pop(); if(vis[x]) continue; vis[x]=1; for(int i=head[x];i;i=ed[i].nex){ int t=ed[i].to,wei=ed[i].val; if(dis[t]>dis[x]+wei){ dis[t]=dis[x]+wei; q.push(make_pair(dis[t],t)); } } } } int main(){ scanf("%d %d %d %d %d",&n,&m,&k,&s,&t); int x,y,z; for(int i=1;i<=m;i++){ scanf("%d %d %d",&x,&y,&z); adde(x,y,z);adde(y,x,z); for(int j=1;j<=k;j++){ adde(x+(j-1)*n,y+j*n,0); //不同层 adde(y+(j-1)*n,x+j*n,0); adde(x+j*n,y+j*n,z); adde(y+j*n,x+j*n,z); } } //这一步:防止hack数据 for(int i=1;i<=k;i++) adde(t+(i-1)*n,t+i*n,0); dij(s); printf("%d",dis[t+k*n]); return 0; }