标签:经典的 problem img image typedef div 根据 c++ its

分层最短路做。以获取钥匙的状态建立分层图,然后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\n");
else printf("%d\n",ans);
}
int main(){
read();
build();
//spfa();
dj();
solve();
return 0;
}

这个不是求最短路了,而是需要求出第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\n");
else printf("%d\n",dis[k*n+n]);
return 0;
}

这道题也可以用分层图解决 +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\n",ans);
return 0;
}
洛谷:
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;
}
//
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;
}
标签:经典的 problem img image typedef div 根据 c++ its
原文地址:https://www.cnblogs.com/shirlybaby/p/13411664.html