这几天写题是真的自闭,连图都不会建了

P1606 [USACO07FEB]白银莲花池

P1979 华容道

题目大意

不可描述,自己看去.

解题思路

bfsbfsbfs预处理+状态连边建图+最短路算法求解

这是这类题一个非常重要的思想.

通过bfsbfsbfs建图将各个状态连接,然后用最短路算法求出最优的状态.

bfsbfsbfs预处理+状态连边建图部分:P1606 [USACO07FEB]白银莲花池

一开始我的想法很简单,就是每个点都进行bfsbfsbfs,处理出每个点能够到达的点.

         if(j-2>=1&&i-1>=1){if(mp[i-1][j-2]!=2&&!check[num[i][j]][num[i-1][j-2]]){check[num[i][j]][num[i-1][j-2]]=true;check[num[i-1][j-2]][num[i][j]]=true;if(mp[i-1][j-2]==0||mp[i][j]==0)add(num[i][j],num[i-1][j-2],1);else add(num[i][j],num[i-1][j-2],0);}}//其中的一部分代码

但是这种方法不可行. 为什么呢?因为这样会有0边权边的出现. 这对于我们统计最短路数是非常不利的.

而且我们会发现一个问题:什么情况下的边应该赋值为1,什么情况应该赋值为0呢?这也是不清楚的.

如图所示,如果按照如上代码的写法,会出现明明只需要2的值但却算出来3的情况,这就需要我们从同一个方向(这样才能真正反映需要填上荷叶的水的数量)统计.比如说我们如果只在指向点为0的时候总边权+1,那么答案就没有错

我们考虑上面那张图,可以发现,我们可以每个0节点为起点,预处理出花费1个荷叶能到达的点,这样就保证了边权全都为1.

同样地,由于每次我们都只是从当前点往前一个点,所以上述“同一个方向统计”的要求也满足. 只不过现在因为以每个0节点为起点,所以我们的“花费一个荷叶”指的就是用一个荷叶把我们0脚下的水填了.

不难发现,如此一来我们就要用bfsbfsbfs找到八个方向每个方向第一个不能不加荷叶跳过去的点(也就是0点)了.

bool tocheck(int x,int y){if(x<1||y<1||x>n||y>m||mp[x][y]==2||vis[x][y])return false;//注意如果是mp=2即一块石头,跳过去是没有意义的return true;
}
void build_map(int x,int y,int u){for(int i=0;i<8;i++){int a=x+drc[i][0],b=y+drc[i][1];if(tocheck(a,b)){vis[a][b]=true;//防止重复走if(mp[a][b]==1)build_map(a,b,u);//如果当前还能不加荷叶继续走,那么就继续往前走else add(u,num[a][b]);//如果是终点mp=4的话,那么就连一条边过去好了;否则说明只加一个荷叶最多就只能走到mp=0的这个位置,要继续往前走,就要加荷叶了}}return ;
}

相当于我们只计起点的0变成1,所以就保证了要填荷叶的水和每点间的边权等

bfsbfsbfs预处理+状态连边建图部分:P1979 华容道

考验状态记录技巧的一道好题!

因为我们只要考虑指定块的位置,而指定块位置的移动和空白块有关,所以只需记录指定块和空白块的位置. 我们可以记录(x1,y1,x2,y2)(x1,y1,x2,y2)(x1,y1,x2,y2)表示指定块在(x1,y1)(x1,y1)(x1,y1),空白块在(x2,y2)(x2,y2)(x2,y2)的状态. 由于空白块可以四方向移动,所以每个状态会向四个状态连边. 这样共有(nm)2(nm)^2(nm)2个状态,总复杂度为O(q(nm)2)O(q(nm)^2)O(q(nm)2),只能通过60%的数据.

但是我们可以发现:只有空白块位于指定块的四方向上,指定块才可以移动. 所以,我们可以记(x1,y1,dir)(x1,y1,dir)(x1,y1,dir)表示指定块在(x1,y1)(x1,y1)(x1,y1),空白块在指定块的dirdirdir方向的状态。这样状态只有4nm4nm4nm个.(程序中是用k∗m∗n+numk*m*n+numk∗m∗n+num实现的,num为指定块的编号)

接下来我们考虑各个状态之间的连边. 首先,空白块和指定块可以交换位置,这两个状态连边的边权为1;

其次,假定空白块在指定块上方,空白块可以通过若干步移动来到空白块下/左/右方。这些状态连边的边权我们可以通过bfsbfsbfs计算出来.

这样就构造出了一张图,先把空白块移动到目标块旁边,之后向目标状态(空白块可以位于指定块的四个方向)做最短路即可.

最短路算法:P1606 [USACO07FEB]白银莲花池

由于我们需要在求最短路的同时求最短路的数目,那么这就是一个最短路计数问题了.

解题关键是,每一个点的最短路径数是由连接它的前一个点决定的.

在边权为1的情况下spfaspfaspfa才成立,否则老老实实用dpdpdp吧

若还没学过最短路计数:P1144 最短路计数

bool check[1010];
int dis[1010];
ll sum[1010];
void spfa(int st){memset(dis,inf,sizeof dis);memset(check,false ,sizeof check);queue<int >q;q.push(st);check[st]=true;dis[st]=0;sum[st]=1;while(!q.empty()){int u=q.front();for(int i=head[u];i;i=e[i].next ){int v=e[i].v ;if(dis[v]>dis[u]+1){dis[v]=dis[u]+1;sum[v]=sum[u];//修改当前最短路数量,此时前面的记录都得推翻重做if(!check[v]){check[v]=true;q.push(v);}}else if(dis[v]==dis[u]+1)sum[v]+=sum[u];//根据加法原理,最短路数量增加}check[u]=false;q.pop();}
}

最短路算法:P1979华容道

用spfaspfaspfa复杂度为O(qknm)O(qknm)O(qknm),可以通过100%的数据.

这东西还有什么好说的吗?

其他

编号方法:如图

1 2 3
4 5 6
7 8 9
10 11 12
num[i][j]=(i-1)*m+j;

程序实现

P1606 [USACO07FEB]白银莲花池

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
struct edge{int v,next;
}e[100010];//考虑30*30*k,k为可以直接到达的点,可能不止8个(一个可以外拓的子节点又可以有8个),所以数组尽量开大
int drc[8][2]={{-2,1},{2,-1},{-1,2},{1,-2},{-2,-1},{-1,-2},{2,1},{1,2}};//预设八个方向
int head[1010],tot;
int tx,ty,sx,sy;
int n,m,ans1;
ll ans2;
void add(int u,int v){e[++tot].v =v;e[tot].next =head[u];head[u]=tot;
}
int mp[51][51],num[51][51];
bool vis[51][51];
bool tocheck(int x,int y){if(x<1||y<1||x>n||y>m||mp[x][y]==2||vis[x][y])return false;return true;
}
void build_map(int x,int y,int u){for(int i=0;i<8;i++){int a=x+drc[i][0],b=y+drc[i][1];if(tocheck(a,b)){vis[a][b]=true;if(mp[a][b]==1)build_map(a,b,u);else add(u,num[a][b]);}}return ;
}//bfs+连边操作
void prepare(){for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(!mp[i][j]||mp[i][j]==3){//找空节点或起点,起点多算的1在spfa后减回来就行了memset(vis,false ,sizeof vis);vis[i][j]=true;build_map(i,j,num[i][j]);}}}
}
bool check[1010];
int dis[1010];
ll sum[1010];//不开long long见祖先
void spfa(int st){memset(dis,inf,sizeof dis);memset(check,false ,sizeof check);queue<int >q;q.push(st);check[st]=true;dis[st]=0;sum[st]=1;while(!q.empty()){int u=q.front();for(int i=head[u];i;i=e[i].next ){int v=e[i].v ;if(dis[v]>dis[u]+1){dis[v]=dis[u]+1;sum[v]=sum[u];if(!check[v]){check[v]=true;q.push(v);}}else if(dis[v]==dis[u]+1)sum[v]+=sum[u];}check[u]=false;q.pop();}
}
int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%d",&mp[i][j]);num[i][j]=(i-1)*m+j;if(mp[i][j]==3)sx=i,sy=j;if(mp[i][j]==4)tx=i,ty=j;}}prepare();spfa(num[sx][sy]);ans1=dis[num[tx][ty]];ans2=sum[num[tx][ty]];if(ans1==inf)printf("-1\n");else printf("%d\n%lld\n",ans1-1,ans2);//记得把起点的边多算的1减回来return 0;
}

P1979 华容道

//规定空格子在指定格子上方(i+1)的状态为num+cnt,下方(i-1)为num+cnt*2,左方(j-1)为num+cnt*3,右方(j+1)为num+cnt*4
#include<bits/stdc++.h>
#define maxn 10010
#define inf 0x3f3f3f3f
using namespace std;
struct edge{int v,w,next;
}e[maxn];
int n,m,q,minn,cnt;
int ex,ey,sx,sy,tx,ty;
int num[101][101],mp[101][101];
int tot,head[maxn];
void add(int u,int v,int w){e[++tot].v =v;e[tot].w =w;e[tot].next =head[u];head[u]=tot;e[++tot].v =u;e[tot].w =w;e[tot].next =head[v];head[v]=tot;
}//双向建边,即两个状态可同步数相互转化
int sum[101][101];//表示到这个格子的步数
bool vis[101][101];
int bfs(int u,int dirc,int sta){//bfs求当前空格在编号为u位置,移动到dirc位置,不能经过sta(指定格子)位置所需最小步数memset(vis,false,sizeof vis);memset(sum,0,sizeof sum);int stx=((sta%m)?(sta/m+1):(sta/m)),sty=((sta%m)?(sta%m):m);//转化回二维形式,注意取模什么的vis[stx][sty]=true;int x=((u%m)?(u/m+1):(u/m)),y=((u%m)?(u%m):m);sum[x][y]=0;vis[x][y]=true;queue<int >q;q.push(u);while(!q.empty()){int w=q.front();q.pop();int a=((w%m)?(w/m+1):(w/m)),b=((w%m)?(w%m):m);if(mp[a-1][b]&&!vis[a-1][b]){vis[a-1][b]=true;sum[a-1][b]=sum[a][b]+1;int ww=num[a-1][b];if(ww==dirc){return sum[a-1][b];break;}q.push(ww);}if(mp[a+1][b]&&!vis[a+1][b]){vis[a+1][b]=true;sum[a+1][b]=sum[a][b]+1;int ww=num[a+1][b];if(ww==dirc){return sum[a+1][b];break;}q.push(ww);}if(mp[a][b+1]&&!vis[a][b+1]){vis[a][b+1]=true;sum[a][b+1]=sum[a][b]+1;int ww=num[a][b+1];if(ww==dirc){return sum[a][b+1];break;}q.push(ww);}if(mp[a][b-1]&&!vis[a][b-1]){vis[a][b-1]=true;sum[a][b-1]=sum[a][b]+1;int ww=num[a][b-1];if(ww==dirc){return sum[a][b-1];break;}q.push(ww);}//以上为向外拓展步数1步}return 0;//false!! 如果到不了,返回的是0,所以输入中空白格子已经在旁边的情况要特判
}
void prepare(){for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(!mp[i][j])continue;if(mp[i+1][j]&&mp[i-1][j]){int k=bfs(num[i+1][j],num[i-1][j],num[i][j]);if(k)add(num[i][j]+cnt,num[i][j]+cnt*2,k);}if(mp[i+1][j]&&mp[i][j+1]){int k=bfs(num[i+1][j],num[i][j+1],num[i][j]);if(k)add(num[i][j]+cnt,num[i][j]+cnt*4,k);}if(mp[i+1][j]&&mp[i][j-1]){int k=bfs(num[i+1][j],num[i][j-1],num[i][j]);if(k)add(num[i][j]+cnt,num[i][j]+cnt*3,k);}if(mp[i-1][j]&&mp[i][j+1]){int k=bfs(num[i-1][j],num[i][j+1],num[i][j]);if(k)add(num[i][j]+cnt*2,num[i][j]+cnt*4,k);}if(mp[i-1][j]&&mp[i][j-1]){int k=bfs(num[i-1][j],num[i][j-1],num[i][j]);if(k)add(num[i][j]+cnt*2,num[i][j]+cnt*3,k);}if(mp[i][j+1]&&mp[i][j-1]){int k=bfs(num[i][j+1],num[i][j-1],num[i][j]);if(k)add(num[i][j]+cnt*3,num[i][j]+cnt*4,k);}if(mp[i+1][j])add(num[i][j]+cnt,num[i+1][j]+cnt*2,1);//if(mp[i-1][j])add(num[i][j]+cnt*2,num[i-1][j]+cnt,1);if(mp[i][j-1])add(num[i][j]+cnt*3,num[i][j-1]+cnt*4,1);//if(mp[i][j+1])add(num[i][j]+cnt*4,num[i][j+1]+cnt*3,1);}}
}//prepare预处理每个点四周(上下左右)互相通达所需的步数//规定空格子在指定格子上方(i+1)的状态为num+cnt,下方(i-1)为num+cnt*2,左方(j-1)为num+cnt*3,右方(j+1)为num+cnt*4
int dis[maxn];
bool check[maxn];
void spfa(int u){memset(dis,inf,sizeof dis);memset(check,false,sizeof check);dis[u]=0;check[u]=true;queue<int >q;q.push(u);while(!q.empty()){int now=q.front();for(int i=head[now];i;i=e[i].next ){int v=e[i].v ;if(dis[v]>dis[now]+e[i].w ){dis[v]=dis[now]+e[i].w ;if(!check[v]){check[v]=true;q.push(v);}}}q.pop();check[now]=false;}
}//对状态求最短路
int main(){scanf("%d%d%d",&n,&m,&q);cnt=n*m;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%d",&mp[i][j]);num[i][j]=(i-1)*m+j;//编号}}prepare();for(int i=1;i<=q;i++){//ex,ey,空白格子;sx,sy,指定格子;tx,ty,目标格子scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);if(sx==tx&&sy==ty){printf("0\n");continue;}//特判1,不用移动,重合int k1=inf,k2=inf,k3=inf,k4=inf;int w=num[ex][ey];minn=inf;if(ex==sx+1&&ey==sy){minn=inf;spfa(num[sx][sy]+cnt);//由于已经在旁边,所以直接求最短路即可for(int j=1;j<=4;j++){minn=min(minn,dis[num[tx][ty]+j*cnt]);//从目标格子的四个方向的距离中选择最小的}if(minn>=inf)printf("-1\n");else printf("%d\n",minn);continue;}else if(ex==sx-1&&ey==sy){minn=inf;spfa(num[sx][sy]+2*cnt);for(int j=1;j<=4;j++){minn=min(minn,dis[num[tx][ty]+j*cnt]);}if(minn>=inf)printf("-1\n");else printf("%d\n",minn);continue;      }else if(ey==sy+1&&ex==sx){minn=inf;spfa(num[sx][sy]+4*cnt);for(int j=1;j<=4;j++){minn=min(minn,dis[num[tx][ty]+j*cnt]);}if(minn>=inf)printf("-1\n");else printf("%d\n",minn);continue; }else if(ey==sy-1&&ex==sx){minn=inf;spfa(num[sx][sy]+3*cnt);for(int j=1;j<=4;j++){minn=min(minn,dis[num[tx][ty]+j*cnt]);}if(minn>=inf)printf("-1\n");else printf("%d\n",minn);continue;}//以上为特判2,不用把空白格子移动到当前格子旁边,已经在旁边了//规定空格子在指定格子上方(i+1)的状态为num+cnt,下方(i-1)为num+cnt*2,左方(j-1)为num+cnt*3,右方(j+1)为num+cnt*4
//同理k1,k2,k3,k4表示的方向if(mp[sx+1][sy]&&bfs(w,num[sx+1][sy],num[sx][sy])){minn=inf;k1=bfs(w,num[sx+1][sy],num[sx][sy]);//k表示先把空白格子移动到指定格子旁边所需的步数,由于有4个方向,所以写了4个ifspfa(num[sx][sy]+cnt);for(int j=1;j<=4;j++){minn=min(minn,dis[num[tx][ty]+j*cnt]);}//这是从目标格子的四个方向的最短距离中取最小的k1+=minn;}if(mp[sx-1][sy]&&bfs(w,num[sx-1][sy],num[sx][sy])){minn=inf;k2=bfs(w,num[sx-1][sy],num[sx][sy]);spfa(num[sx][sy]+cnt*2);for(int j=1;j<=4;j++){minn=min(minn,dis[num[tx][ty]+j*cnt]);}k2+=minn;}if(mp[sx][sy-1]&&bfs(w,num[sx][sy-1],num[sx][sy])){minn=inf;k3=bfs(w,num[sx][sy-1],num[sx][sy]);spfa(num[sx][sy]+cnt*3);for(int j=1;j<=4;j++){minn=min(minn,dis[num[tx][ty]+j*cnt]);}k3+=minn;}if(mp[sx][sy+1]&&bfs(w,num[sx][sy+1],num[sx][sy])){minn=inf;k4=bfs(w,num[sx][sy+1],num[sx][sy]);spfa(num[sx][sy]+cnt*4);for(int j=1;j<=4;j++){minn=min(minn,dis[num[tx][ty]+j*cnt]);}k4+=minn;}//以上为一般情况minn=min(k1,k2);minn=min(k3,minn);minn=min(k4,minn);//求最小值if(minn>=inf){printf("-1\n");continue;}//有加法,所以是大于等于printf("%d\n",minn);}return 0;
}

题后总结

建图真的很重要!以后看到这一类型的题,就想一下可不可以建图抽象化地表示状态,然后通过最短路算法表示状态的改变,从而求解.

可以通过条件推知要建什么样的图,建的图没有一定的要求,入乡随俗就好. 比如说双向图啊,单向图啊,都是不一定的.

P1606 [USACO07FEB]白银莲花池 P1979 华容道(bfs预处理+状态连边建图+最短路算法求解,最短路计数)相关推荐

  1. 【USACO】青铜莲花池[2]

    前言 搜索到这篇文章的朋友,那么很巧了,我们多半是一个学校的,为什么呢?因为这道题叫白银莲花池.. 题目 [问题描述] FJ建造了一个美丽的池塘,用于让奶牛们锻炼.这个长方形的池子被分割成了 M 行和 ...

  2. luogu P1979 华容道

    P1979 华容道 题目描述 [问题描述] 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少 ...

  3. P1606 [USACO07FEB]荷叶塘Lilypad Pond(最短路计数)

    P1606 [USACO07FEB]荷叶塘Lilypad Pond 题目描述 FJ has installed a beautiful pond for his cows' aesthetic enj ...

  4. 故事公园-—昆明莲花池

    每个公园都有自己的地理特征,奇风异俗,各存其韵.昆明莲花池公园则因几处遗址以及涉及的故事,让人们留连往返,奕奕不舍,他(她)们的故事让世人感叹! 这里的遗址其中有南明末代皇帝的墓地,陈圆圆的梳妆楼,现 ...

  5. 算法提高课-图论-单源最短路的建图方式-AcWing 920. 最优乘车:bfs求最短路、建图

    题目分析 来源:acwing 分析: 本题难在抽象建图上,这里采用的建图方式是:同一条公交线路上,前面的站点都可以连一条有向边到其后面的站点,且边权都为1. 由于边权都是1,可以用bfs来求最短路. ...

  6. Android华容道之一步一步实现-4-图像块移动算法

    下一个关键点就是图像块的移动,以如图为例. 假设空格处于第二行第三格,那么此时只有触摸第二行以及第三列的图像块的时候才需要移动图像块,因为别的图像块不能移动. 当触摸发生在合法的图像块的时候,即上面图 ...

  7. vector邻接表建图+DFS+BFS

    以边操作为主的图用边集数组存储比较好,相比链式前向星,vector建图更容易懂. #include <iostream> #include <cstdio> #include ...

  8. 链式前向星模板 建图+dfs+bfs+dijkstra

    边没有用struct封装起来,节点和边的计数起点如果不符合习惯可以稍作修改 建图+DFS+BFS #include <cstdio> #include <cstring> #i ...

  9. CF-1209 F. Koala and Notebook(建图BFS)

    CF-1209 F. Koala and Notebook(建图BFS) 题目链接 题意 n个城市m个双向边,从点1可以到达任何点,把点1到到其他点所经过的边写成一行可以得到一个大数,你的任务使得这个 ...

  10. dijkstra算法_Python实现图的经典DFS、BFS、Dijkstra、Floyd、Prim、Kruskal算法

    讲在前面的话,图的算法太多,理论知识肯定一篇文章讲不完,关于理论知识大家可以参考教材Sedgewick的<算法>或reference的链接,本文主要还是想在一篇文章中记录六种算法的Pyth ...

最新文章

  1. 强烈推荐Oracle的入门心得
  2. BAT华为美团头条面试考什么?这份GitHub万星资源,告诉你面试题+答案+出题人分析...
  3. 在DLL编程中调用模版类时出现的类似class“XXX”需要有 dll 接口由 class“XXX”的客户端使用的warning的解决方案...
  4. PostgreSQL cheatSheet
  5. 怎样实现关闭connection时自动关闭Statement和ResultSet
  6. “adb不是内部或外部命令,也不是可运行的程序或批量文件“
  7. java安全初始化_java安全编码指南之:声明和初始化
  8. 小球弹起次数及高度(python)
  9. 《密码与安全新技术专题》第11周作业
  10. google bookmarks的书签分类的技术
  11. Oracle学习总结2-数据处理
  12. 数据结构11——KMP
  13. Linux一些基本概念
  14. 【信号与系统实验】实验七 音频信号的采集和传输
  15. 利用iTunes传输大型文件电脑--ipad
  16. Android投屏神器scrcpy
  17. [转][RabbitMQ+Python入门经典] 兔子和兔子窝
  18. 集成灶哪个品牌性价比高质量好,集成灶品牌排行榜前十
  19. 全球 13 家最雄心勃勃的元宇宙公司
  20. stackelberg博弈_2020年全国博弈论与实验经济学研究会学术年会成功举办

热门文章

  1. python基础知识-12-模块的了解
  2. Android L 的手机,安卓新系统Android L上手评测:改变很大
  3. 当数学题加上了程序员思想
  4. appium driver参数及命令行参数
  5. 软件测试背景对渗透测试有用吗,软件测试与渗透测试那个工作有前途
  6. idea、webstorm使用过程出现问题
  7. 显示农历天气时钟小部件下载_玛雅日历安卓版下载|玛雅日历app下载_v5.3.2
  8. 服务器多网卡同一网段
  9. cp: omitting directory ‘./.local/lib/python3.9/site-packages/.’
  10. excel 一列的数据除以另一列