广搜优化题目总结
电路维修*
题面
这道题之前打过,但那时候打题太水了,没有真正掌握这道题的知识点。(果然我还是太蒻了)
这道题的解法是先建边,对于每一个单位正方形,将有边相连的两个对角建一条长度为0的无向边,另外两个对角建一条长度为1的无向边。然后可以跑最短路或者用双端队列bfs(0-1bfs)。
跑最短路的话要注意由于是网格图spfa会炸掉,所以要用堆优化的dijkstra( ~~不会不加堆优化的dijkstra (^_^)~~ )
因为在练习广搜,所以我用的是双端队列bfs
当我们遇到边权只有1的情况时,显然直接bfs,然后第一次访问到某个点的“时间”就是到它的最短路径
当边权同时存在1和0两种情况时,我们就可以采用双端队列bfs,也叫作0-1bfs,就是访问时,若边权为0,则把目标点v放在队首,若边权为1,则把目标点放在队尾,这样是为了使得边权为0时的目标点v先被访问。
我按照这个思路做了,但却WA了,后来我一看题解,队列里维护的居然不是点,而是边,我一直想不通为什么,找了半天也只有题解模糊地写到,然后我就自己手玩了一把,发现如果是维护点,那么有可能会出现这样的情况:显然如果边权为0,那么目标点v与点u应该处于广搜的同一层,但如果是维护点,就可能会出现点v在被点u访问到之前被下一层的点提前访问了(也就是由这一层的点通过边权为1的边得到的目标点vv访问过来(因为如果是维护点,那么就会像是一个深搜的过程,会在我把这一层访问完之前,下一层的点先把这一层的点访问了))。但如果我们是维护边的话,那么将会起到一个滞留的作用,能够留给我时间,让我先把这一层的访问完了,在访问下一层。
这道题我还有一个收获就是知道了链式前向星还能储存入边,以前我的不知道来着,,,
这是我WA掉的访问点的代码(注释掉的是我手玩搜索过程的代码)
#include <iostream>
#include <queue>
#include <deque>
#include <cstdio>
#include <cstring>
using namespace std;
#define Maxn 280000
int n,m;
deque<int>q;
int fir[Maxn],nxt[Maxn*2],vv[Maxn*2],edg[Maxn*2];
int tot=0,ans=0;
int vis[Maxn],dis[Maxn];
void add(int u,int v,int w)
{
nxt[++tot]=fir[u];
fir[u]=tot;
vv[tot]=v;
edg[tot]=w;
}
//int room[1000];
int id(int x,int y){return (x-1)*(m+1)+y;}
void bfs()
{
memset(dis,-1,sizeof(dis));
q.push_front(id(1,1));vis[id(1,1)]=1;dis[id(1,1)]=0;
while(!q.empty())
{
// int cnt=0;
// printf("q:");
// while(!q.empty())
// {
// room[++cnt]=q.front();q.pop_front();
// printf("%d ",room[cnt]);
// }
// printf("\n");
// for(int i=cnt;i>=1;i--)q.push_front(room[i]);
int u=q.front();q.pop_front();
// printf("u=%d dis[u]=%d\n",u,dis[u]);
for(int i=fir[u];i;i=nxt[i])
{
// printf("u->v: %d->%d\n",u,vv[i]);
int v=vv[i];
if(vis[v]==1)continue;
vis[v]=1;
dis[v]=dis[u]+edg[i];
if(v==id(n+1,m+1))return;
if(edg[i]==0)q.push_front(v);
else q.push_back(v);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
string str;cin>>str;
for(int j=1;j<=m;j++)
{
int x1=id(i,j),x2=id(i+1,j+1),y1=id(i+1,j),y2=id(i,j+1);
if(str[j-1]=='/')
{
add(x1,x2,1);add(x2,x1,1);
add(y1,y2,0);add(y2,y1,0);
}
else
{
add(x1,x2,0);add(x2,x1,0);
add(y1,y2,1);add(y2,y1,1);
}
}
}
bfs();
/* for(int i=1;i<=n+1;i++)
for(int j=1;j<=m+1;j++)
printf("id(%d,%d)=%d\n",i,j,id(i,j));
for(int i=1;i<=id(n+1,m+1);i++)
printf("dis[%d]=%d\n",i,dis[i]);
printf("id(n+1,m+1)=%d\n",id(n+1,m+1));printf("dis[%d]=%d\n",id(n+1,m+1),dis[id(n+1,m+1)]);*/
if(dis[id(n+1,m+1)]==-1)printf("NO SOLUTION\n");
else printf("%d\n",dis[id(n+1,m+1)]);
return 0;
}
这是正解
#include <iostream>
#include <queue>
#include <deque>
#include <cstdio>
#include <cstring>
using namespace std;
#define Maxn 2800000
int n,m;
deque<int>q;
int fir[Maxn],nxt[Maxn*4],uu[Maxn*4],vv[Maxn*4],edg[Maxn*4];
int tot=0,ans=0;
int vis[Maxn],dis[Maxn];
void add(int u,int v,int w)
{
nxt[++tot]=fir[u];
fir[u]=tot;
uu[tot]=u;
vv[tot]=v;
edg[tot]=w;
}
int id(int x,int y){return (x-1)*(m+1)+y;}
void bfs()
{
memset(dis,-1,sizeof(dis));
memset(vis,0,sizeof(vis));
q.clear();
q.push_front(id(1,1));vis[id(1,1)]=1;dis[id(1,1)]=0;
while(!q.empty())
{
int e=q.front();q.pop_front();
int u=uu[e];int v=vv[e];
if(vis[v]==1)continue;
vis[v]=1;
dis[v]=dis[u]+edg[e];
if(v==id(n+1,m+1))return;
for(int i=fir[v];i;i=nxt[i])
{
if(vis[vv[i]]==1)continue;
if(edg[i]==0)q.push_front(i);
else q.push_back(i);
}
}
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
tot=0;memset(fir,0,sizeof(fir));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
string str;cin>>str;
for(int j=1;j<=m;j++)
{
int x1=id(i,j),x2=id(i+1,j+1),y1=id(i+1,j),y2=id(i,j+1);
if(str[j-1]=='/')
{
add(x1,x2,1);add(x2,x1,1);
add(y1,y2,0);add(y2,y1,0);
}
else
{
add(x1,x2,0);add(x2,x1,0);
add(y1,y2,1);add(y2,y1,1);
}
}
}
bfs();
if(dis[id(n+1,m+1)]==-1)printf("NO SOLUTION\n");
else printf("%d\n",dis[id(n+1,m+1)]);
}
return 0;
}
魔板
可能性一共用8!=40320种
种类极少,所以直接用map去重(注意map速率较慢,听说插入和查询都是logn),也可以用康拓展开(一个排列在全排列中的顺序)来做
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#include<cstdlib>
using namespace std;
string str1,ansstr,cstr;int a[10];int pd=0;
queue <string> q;
map<string,string>m;
void opA(string str)
{
string sstr="";
for(int i=7;i>=4;i--)sstr+=str[i];
for(int i=3;i>=0;i--)sstr+=str[i];
if(m.count(sstr)==0)
{
q.push(sstr);
m[sstr]=m[str]+'A';
}
}
void opB(string str)
{
string sstr="";
sstr+=str[3];sstr+=str[0];sstr+=str[1];sstr+=str[2];
sstr+=str[5];sstr+=str[6];sstr+=str[7];sstr+=str[4];
if(m.count(sstr)==0)
{
q.push(sstr);
m[sstr]=m[str]+'B';
}
}
void opC(string str)
{
string sstr="";
sstr+=str[0];sstr+=str[6];sstr+=str[1];sstr+=str[3];
sstr+=str[4];sstr+=str[2];sstr+=str[5];sstr+=str[7];
if(m.count(sstr)==0)
{
q.push(sstr);
m[sstr]=m[str]+'C';
}
}
void bfs()
{
q.push("12345678");
m["12345678"]="";
while(q.size()!=0)
{
string str=q.front();
q.pop();
opA(str);
opB(str);
opC(str);
if(m.count(ansstr)!=0)
{
cout<<m[ansstr].size()<<endl<<m[ansstr];
return;
}
}
}
int main()
{
getline(cin,cstr);
for(int i=1;i<=8;i++)ansstr+=cstr[2*i-2];
bfs();
return 0;
}
Knight Moves
双向bfs裸题
双向bfs有两种:
1.交替扩展
2.个数较少的方向先扩展
显然方法2会更优
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
struct qp{
int x,y;
}q[2][100000];
int n;
int ans=0;
int l[2],r[2];
int v[2][310][310],dis[2][310][310];
int dx[9]={0,1,1,2,2,-1,-1,-2,-2};
int dy[9]={0,2,-2,1,-1,2,-2,1,-1};
int check(int x,int y){if(x>=0&&x<=n&&y>=0&&y<=n)return 1;else return 0;}
int expand(int k)
{
int x=q[k][l[k]].x,y=q[k][l[k]].y;
int d=dis[k][x][y];
for(int i=1;i<=8;i++)
{
if(check(x+dx[i],y+dy[i])==1&&!v[k][x+dx[i]][y+dy[i]])
{
v[k][x+dx[i]][y+dy[i]]=1;
r[k]++;q[k][r[k]].x=x+dx[i];q[k][r[k]].y=y+dy[i];
dis[k][x+dx[i]][y+dy[i]]=d+1;
if(v[1-k][x+dx[i]][y+dy[i]]==1)
{
ans=dis[k][x+dx[i]][y+dy[i]]+dis[1-k][x+dx[i]][y+dy[i]];
return 1;
}
}
}
return 0;
}
void bfs()
{
if(q[0][1].x==q[1][1].x&&q[0][1].y==q[1][1].y)
{
ans=0;return;
}
l[0]=r[0]=1;l[1]=r[1]=1;
v[0][q[0][1].x][q[0][1].y]=1;
v[1][q[1][1].x][q[1][1].y]=1;
while(l[0]<=r[0]&&l[1]<=r[1])
{
if(r[0]-l[0]<r[1]-l[1])
{
if(expand(0)==1)return;
l[0]++;
}
else
{
if(expand(1)==1)return;
l[1]++;
}
}
}
int main()
{
n=7;
string str1,str2;
while(cin>>str1>>str2)
{
memset(v,0,sizeof(v));
memset(dis,0,sizeof(dis));
q[0][1].x=str1[0]-'a';q[0][1].y=str1[1]-'1';q[1][1].x=str2[0]-'a';q[1][1].y=str2[1]-'1';
bfs();
printf("To get from ");cout<<str1;printf(" to ");cout<<str2;printf(" takes %d knight moves.\n",ans);
}
return 0;
}
移动玩具(和棋盘游戏重题)
典型的双向bfs裸题,虽然好像数据水,单向也能过。
结构体里同时存一个数组和id号(二进制判重),虽然感觉可以通过一些神奇的位运算操作,只用存一个id号
还有memcpy的用法,听说和memset一样,在不同环境下与for赋值的时间相比各有优劣
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
struct qp{
int id,a[6][6];
}q[2][100000];
int l[2],r[2];
int vis[2][100000];
int dx[5]={0,1,-1,0,0};
int dy[5]={0,0,0,1,-1};
int check(int x,int y)
{
if(x>=1&&x<=4&&y>=1&&y<=4)return 1;
return 0;
}
int ans=0;
int expand(int k)
{
// printf("hahah\n");
int hashs=0;
for(int ii=1;ii<=4;ii++)
for(int jj=1;jj<=4;jj++)
{
hashs=hashs*2+q[k][l[k]].a[ii][jj];
}
for(int i=1;i<=4;i++)
{
for(int j=1;j<=4;j++)
{
if(q[k][l[k]].a[i][j]==0)continue;
for(int kk=1;kk<=4;kk++)
{
int tx=i+dx[kk],ty=j+dy[kk];
if(check(tx,ty)==0)continue;
// printf("pass test1 i=%d j=%d kk=%d tx=%d ty=%d\n",i,j,kk,tx,ty);
if(q[k][l[k]].a[tx][ty]==1)continue;
// printf("pass test2 i=%d j=%d kk=%d q[k][l[k]].a[tx][ty]=%d\n",i,j,kk,q[k][l[k]].a[tx][ty]);
int hashe=0;
q[k][l[k]].a[tx][ty]=1;q[k][l[k]].a[i][j]=0;
for(int ii=1;ii<=4;ii++)
for(int jj=1;jj<=4;jj++)
{
hashe=hashe*2+q[k][l[k]].a[ii][jj];
}
//printf("hashs=%d hashe=%d\n",hashs,hashe);
if(vis[k][hashe]!=0)
{q[k][l[k]].a[tx][ty]=0;q[k][l[k]].a[i][j]=1;continue;}
vis[k][hashe]=vis[k][hashs]+1;
r[k]++;
for(int ii=1;ii<=4;ii++)
for(int jj=1;jj<=4;jj++)
{
q[k][r[k]].a[ii][jj]=q[k][l[k]].a[ii][jj];
}
if(vis[1-k][hashe]!=0)
{
ans=vis[k][hashe]-1+vis[1-k][hashe]-1;
// printf("vis1=%d vis2=%d\n",vis[k][hashe],vis[1-k][hashe]);
return 1;
}
q[k][l[k]].a[tx][ty]=0;q[k][l[k]].a[i][j]=1;
}
}
}
return 0;
}
void bfs()
{
l[0]=r[0]=1;l[1]=r[1]=1;
int hashs=0,hashe=0;
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
{
hashs=hashs*2+q[0][1].a[i][j];hashe=hashe*2+q[1][1].a[i][j];
}
if(hashs==hashe){ans=0;return;}
vis[0][hashs]=1;vis[1][hashe]=1;
while(l[0]<=r[0]&&l[1]<=r[1])
{
if(r[0]-l[0]<r[1]-l[1])
{
if(expand(0))return;
l[0]++;
}
else
{
if(expand(1))return;
l[1]++;
}
}
}
int main()
{
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
scanf("%1d",&q[0][1].a[i][j]);
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
scanf("%1d",&q[1][1].a[i][j]);
bfs();printf("%d\n",ans);
return 0;
}
Keyboarding
这个翻译确实很差,所以需要注意的点有:
1.文中“与当前位置不同的字符”的意思是“与当前位置的字符不同的字符"
2.多组数据
剪枝一:首先我们需要先预处理每个点往四个方向移动分别可以到达哪个点
那么问题来了:怎么判重呢?
每个位置是可以重复到达的,所以记录之前到达过的位置的判重方法是错误的,
那么对于一个点往右走再往左走,回到原位置的大量重复状态这种以前是通过判重来剪枝的情况,现在该如何剪枝呢?根据这道题的特性,我们可以得到以下的剪枝方法:
剪枝二:记录每个点之前扫描到该点时打印的字符串的最大长度,如果我们又搜索到该点,且当前打印的字符串长度小于等于之前记录的这个点打印的最长长度,则可以剪枝
我看到洛谷题解里的大部分题解是把选择和移动分开处理的,其实并不需要,因为每到一个点,就用处理起点的方法,直接能打印多少就贪心打印完就可以了。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
int n,m,len;
struct node{
int x,y;
}to[55][55][6];
char ch[55][55];
char goal[10010];
int dx[5]={0,1,-1,0,0};
int dy[5]={0,0,0,1,-1};
int check(int x,int y)
{
if(x>=1&&x<=n&&y>=1&&y<=m)return 1;
return 0;
}
void Deal_first()
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=1;k<=4;k++)
{
int tx=i+dx[k],ty=j+dy[k];
if(check(tx,ty)==0)continue;
while(ch[tx][ty]==ch[i][j]&&check(tx,ty)==1)
{
tx+=dx[k];ty+=dy[k];
}
if(!check(tx,ty))continue;
to[i][j][k].x=tx;to[i][j][k].y=ty;
}
}
}
}
struct jp{
int x,y,cnt,ans;
};
queue<jp> q;
int vis[55][55];
void bfs()
{
int tim=1;
while(ch[1][1]==goal[tim])tim++;
q.push((jp){1,1,tim,tim-1});
vis[1][1]=tim;
while(!q.empty())
{
jp A=q.front();q.pop();
for(int i=1;i<=4;i++)
{
int tx=to[A.x][A.y][i].x,ty=to[A.x][A.y][i].y;
if(check(tx,ty)==0)continue;
int timm=0;
while(ch[tx][ty]==goal[A.cnt+timm])timm++;
if(A.cnt+timm==len+1)
{
printf("%d\n",A.ans+timm+1);
return ;
}
if(vis[tx][ty]<A.cnt+timm)vis[tx][ty]=A.cnt+timm;
else continue;
q.push((jp){tx,ty,A.cnt+timm,A.ans+timm+1});
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
while(!q.empty())q.pop();
memset(vis,0,sizeof(vis));
memset(to,0,sizeof(to));len=0;
for(int i=1;i<=n;i++)scanf("%s",ch[i]+1);
scanf("%s",goal+1);
len=strlen(goal+1);
goal[++len]='*';
Deal_first();
bfs();
}
return 0;
}
山峰和山谷
bfs水题
直接枚举每个未访问的点跑一遍bfs就可以了,一开始我还在想储存1000000个queue,然后再跑,后来发现求bfs的时候顺带扫描就完了。
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
#define mp make_pair
#define pr pair<int,int>
int n;
int a[1005][1005],vis[1005][1005];
int dx[9]={0,1,-1,0,0,1,1,-1,-1};
int dy[9]={0,0,0,-1,1,1,-1,1,-1};
int check(int x,int y)
{
if(x>=1&&x<=n&&y>=1&&y<=n)return 1;
return 0;
}
int pd1,pd2;
queue<pr> q;
void bfs(int x,int y)
{
q.push(mp(x,y));vis[x][y]=1;
while(!q.empty())
{
pr A=q.front();q.pop();
for(int i=1;i<=8;i++)
{
int tx=A.first+dx[i],ty=A.second+dy[i];
if(check(tx,ty)==0)continue;
if(a[tx][ty]==a[A.first][A.second]&&!vis[tx][ty])
{
vis[tx][ty]=1;q.push(mp(tx,ty));
}
if(a[tx][ty]>a[A.first][A.second])pd2=0;
if(a[tx][ty]<a[A.first][A.second])pd1=0;
}
}
}
int main()
{
int ans1=0,ans2=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(!vis[i][j])
{
pd1=1;pd2=1;
bfs(i,j);
ans1+=pd1;ans2+=pd2;
}
}
}
printf("%d %d",ans2,ans1);
return 0;
}
未完待续...(已update完于2019.8.15)