关于搜索的优化
- 搜索的问题可能在搜索的思路方面很难有(很难想到)什么大的优化,其思路可能和水分的思路是大同小异的。
- 搜索的限制条件有两个
- 空间:我们需要通过仔细的挖掘状态的规律,量化状态来压缩内存。
- 时间:我们可以通过不同的搜索方式和A*的优化来加速
对于不同的搜索方式
- 我们先来讲讲最简单的两种搜索方式
- DFS:我们通过深度优先的搜索方式,只有在搜索完了所以的情况以后才会得到最优解,往往时间复杂度大。
- BFS:我们通过广度优先的搜索方式,一层一层的搜索只要搜索到了解就是最优解,可是要消耗很大的空间。
- 当我们发现了他们的不足之后,新的搜索方式(新的变形)就出现了。
- 双向BFS:既然BFS有这样的限制,为什么不从两端都开始搜索呢?由于BFS的特性,两端依次一层一层的扩张开来,他们第一次产生的交点不就是最优解了吗?可惜的是双向广搜在一些没有或者难以实现终点回退逆操作的时候最束手无策了。
- IDA*:这是DFS的一种变形,由于DFS只有在搜索到最深的时候才会停止搜索。所以我们可以通过限制它的深度来搜索,即每次给它一个深度限制只搜索深度限制内的节点。
对于搜索的A*优化
- h(x) 估价函数:我们可以用 h(x) 来表示当前状态到达目标状态的最小值(或是一个相对值)*(由于在一些BFS h(x) 只要是正确的我们就可以把它适当的放大把它作为一种相对值, h(x) 相差越大其优化效果往往根明显)。
- DFS:我们发现DFS在搜索的时候是漫无目的的即使搜索到了一个解也没有什么用,所以我们可以在搜索到了一个解后用 h(x) 和其比较一下,如果 h(x) 都比它大了,就直接停止搜索了。
- BFS:我们可以通过优先队列来从 h(x) 小的开始搜索。
例题
哆啦A梦
- 由于正反向的操作完全一样,我们可以很容易的想到用双向广搜。我们就把它当模板吧。
#include
using namespace std;
const int M=1e6;
int cas,D[2][M+5],R[2][M+5],l,r,w,S,T;
struct P{int x,s;bool f;}t,tmp,Q[M*5];
int val(int x,bool f){
if(R[f][x]!=cas)return -1;
return D[f][x];
}
void insert(int x,bool f,int c){
if(R[f][x]==cas)return;
R[f][x]=cas,D[f][x]=c;
tmp.x=x;tmp.f=f;tmp.s=c;
Q[r++]=tmp;
}
void solve(){
cas++;l=r=0;
scanf("%d%d",&S,&T);
insert(S,0,0);insert(T,1,0);
while(l
贪吃蛇
- 首先我们需要考虑空间复杂度
如果在k=8时我们需要记录8个点的坐标于是我们需要0.5*8=4个int的大小去存
可是蛇上每个部分的相对位置可以所有4进制存下
- 如图
[1]####
[2]##[6]
[3][4][5]
- 对于这样一条蛇我们可以记录蛇的头坐标
char rx[]={0,1,0,-1};
char ry[]={1,0,-1,0};
void add(char x,char y){
char i=0;for(;i<4;i++)
if(x==tx+rx[i]&&y==ty+ry[i])break;
A<<=2;A+=i;
}
- 用A记录其相对位置于是每条蛇可以用0.5+0.25*3个int记录,于是内存方面的问题就解决了
用 A * 优化BFS
- 我们可以用一遍BFS预处理出每个点到达终点的最小距离(不考虑蛇身)。把 h(i)=dis[x][y]+step 作为估价函数。
/*预处理出dis[x][y]*/
bool check(node a,int i){
a.x-=rx[i]
a.a>>=2
if(a.x<=n&&a.x>=1&&a.y<=m&&a.y>=1&&!s[a.x][a.y])
if(!mark[a.x][a.y][a.a]){
Q.push(a)
}
}
void ck(R a){
if(a.x<=n&&a.x>0&&a.y<=m&&a.y>0&&!s[a.x][a.y]&&!dis[a.x][a.y]){
dis[a.x][a.y]=a.s
}
}
void init(){
dis[1][1]=1
while(l
void bfs(){
while(!Q.empty()){
memcpy(s,S,sizeof(S));
nw=Q.top();Q.pop();
tx=nw.x;ty=nw.y;A=nw.a;
if(nw.step>=100)break;
if(tx==1&&ty==1){printf("%d\n",nw.step);return;}
s[tx][ty]=1;
for(int i=k-2;i>=0;i--){
tx+=rx[A/v[i]%4];
ty+=ry[A/v[i]%4];
s[tx][ty]=1;
}nw.step+=1;
for(int i=0;i<4;i++)check(nw,i);
}puts("-1");
}
用A*优化DFS
void dfs(char x,char y,char step,short A){
if(dis[x][y]+step>=now_ans)return;
if(x==1&&y==1){now_ans=step;return;}
if(mark[tx][ty][A])return;
mark[tx][ty][A]=1;
sx=x,sy=y;
memcpy(s,S,sizeof(S));
s[sx][sy]=6;
for(int i=k-2;i>=0;i--){
sx+=rx[A/v[i]%4];
sy+=ry[A/v[i]%4];
s[sx][sy]=6;
}A/=4;
for(int i=0;i<4;i++){
B=A+v[k-2]*i;
tx=x-rx[i],ty=y-ry[i];
if(!check(tx,ty))continue;
if(mark[tx][ty][B])contienu;
mark[tx][ty][B]=1;
dfs(tx,ty,step+1,B);
mark[tx][ty][B]=0;
}
}
序列还原
双向广搜
- 很容易想到暴力的BFS算法又发现在这里正逆操作差不多故比较容易可以想到双向广搜
- 可是由于map太慢了,不加上”排第几”映射只有90分。(性价比比较高了)
#include
#define For(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
const int M=20,N=1e5+5;
ll B[M],T,S;
int n,m,x,C[N][2];
struct P{
ll x;int s;bool f;
}t,s;
queueQ;
mapint>K[2];
int get(ll x,int c){
c=n-c;
return(x/B[c])%13;
}
ll Swap(ll x,int a,int b){
int Ab=get(x,a),Bb=get(x,b);
x+=(Ab-Bb)*B[n-b];
x+=(Bb-Ab)*B[n-a];
return x;
}
int main(){
scanf("%d %d",&n,&m);
B[0]=1;For(i,1,13)B[i]=B[i-1]*13;
For(i,1,n)scanf("%d",&x),T=T*13+i,S=S*13+x;
for(int i=1;i<=m;i++)
scanf("%d %d",&C[i][0],&C[i][1]);
K[0][S]=0;K[1][T]=0;
Q.push((P){S,0,0});
Q.push((P){T,0,1});
while(!Q.empty()){
bool f=Q.front().f;
while(f==Q.front().f){
s=Q.front();Q.pop();
ll x=s.x,y;
bool g=s.f;
int step=s.s;
if(K[!g].count(x)){
printf("%d\n",K[!g][x]+step);
return 0;
}step++;
For(i,1,m){
int a=C[i][0],b=C[i][1];
y=Swap(x,a,b);
if(K[g].count(y))continue;
K[g][y]=step;
Q.push((P){y,step,g});
}
}
}
return 0;
}
A*优化的BFS
- 我们可以把当前串和结果的不同处来作为 h(x) 的一部分即 h(x)=difference+step 。讲道理,虽然这可以保证正确性却不一定保证最优性,因为当前状态变为最终状态的最小步数是 difference+1/2 ,又这里我们为了加强优化效果就不这样处理了,我们可以想当然的认为,前状态变为最终状态的步数和 difference 相差较小。
#include
#define For(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
const int M=1005;
ll B[M],T,S;
int n,m,ans=2e9,C[M][2];
mapbool>mark;
struct node{
ll x;int f,s;
bool operator<(const node&A)const{return A.fQ;
int val(ll x){
int res=0;
for(int i=n;i>=1;i--){
if(x%13!=i)res++;
x/=13;
}return res;
}
void insert(ll x,int s){
if(mark[x])return;
int f=val(x)+s;
tmp.f=f;tmp.x=x;tmp.s=s;
Q.push(tmp);
}
int get(ll x,int c) {
c=n-c;
return(x/B[c])%13;
}
ll Swap(ll x,int a,int b) {
int Ab=get(x,a),Bb=get(x,b);
x+=(Ab-Bb)*B[n-b];
x+=(Bb-Ab)*B[n-a];
return x;
}
void pt(ll S){
for(int i=1;i<=n;i++){
printf("%d ",S%13);
S/=13;
}puts("");
}
int main(){
scanf("%d %d",&n,&m);
B[0]=1;
For(i,1,13)B[i]=B[i-1]*13;
For(i,1,n){
int x;
scanf("%d",&x);
T=T*13+i,S=S*13+x;
}
For(i,1,m)
scanf("%d %d",&C[i][0],&C[i][1]);
insert(S,0);
while(!Q.empty()){
t=Q.top();
Q.pop();
ll x=t.x;
int s=t.s;
mark[x]=1;
if(x==T){
printf("%d",s);
return 0;
}s++;
for(int i=1;i<=m;i++){
int a=C[i][0],b=C[i][1];
ll y=Swap(x,a,b);
insert(y,s);
}
}puts("NEMA");
return 0;
}
A*优化的DFS
- 我们发现题目给了很多对 a,b 此时我们可以把它看成 (a,b,1) 这样一条边。通过 Floyd 来得到交换任意两个位置的 cost 。所以对于一些会让总的 cost 增大的操作我们就不进行了。
- 同时我们的 h(x) 也就处理出来了。
#include
#define For(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
const int M=1005;
int n,m,ans=2e9,A[M],B[M],S[M];
int fa[M],dis[M][M],mp[M];
void Min(int &a,int b){if(a>b)a=b;}
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
void dfs(int res,int st){
if((res+1)/2+st>=ans)return;
if(!res){Min(ans,st);return;}
For(i,1,m){
int a=A[i],b=B[i];
int nxt=res-dis[S[a]][a]-dis[S[b]][b];
swap(S[a],S[b]);
nxt+=dis[S[a]][a]+dis[S[b]][b];
if(nxt>=res){swap(S[a],S[b]);continue;}
dfs(nxt,st+1);
swap(S[a],S[b]);
}
}
int main(){
memset(dis,63,sizeof(dis));
scanf("%d %d",&n,&m);
ans=2e9;
For(i,1,n)scanf("%d",&S[i]),fa[i]=i,mp[S[i]]=i;
For(i,1,m){
scanf("%d %d",&A[i],&B[i]);
fa[find(A[i])]=find(B[i]);
dis[A[i]][B[i]]=dis[B[i]][A[i]]=1;
}
For(i,1,n)dis[i][i]=0;
For(k,1,n)For(i,1,n)For(j,1,n)Min(dis[i][j],dis[i][k]+dis[k][j]);
For(i,1,n)For(j,1,n)if(dis[i][j]>1)++dis[i][j];
int res=0;
For(i,1,n){
res+=dis[i][mp[i]];
if(find(mp[i])!=find(i)){puts("NEMA");return 0;}
}dfs(res,0);
printf("%d\n",ans);
return 0;
}