目錄php
搞了半個月的搜索,寫的頭都要炸了,不過貌似仍是有提高的。node
終於把書上的基礎搜索算法康完了,因而有了這篇文章。ios
其實就是在本書基礎上加上本身的理解(做爲OIer,感受看電腦就是比看書舒服),如下是正文部分。算法
其實和搜索的關係不大,但基於搜索實現,並且還蠻有用的。框架
這個...很基礎了,直接上代碼吧:ide
void dfs(int x){ v[x]=1; for(int i=head[x];i;i=next[i]){ int y=ver[i]; if(v[y]) continue; dfs(y); } }
複雜度 \(O(N+M)\) 。函數
按上述方法遍歷每個節點的順序被稱爲樹的 \(dfs\) 序。優化
而按上述順序按第一次被遍歷的時間給每個節點 1~N 的標記,這個標記被稱爲時間戳。ui
已知根節點的深度爲 0 ,若節點 \(x\) 的深度爲 \(d[x]\) ,其子節點 \(y\) 的深度爲 \(d[y]=d[x]+1\) 。spa
代碼見下:
void dfs(int x){ v[x]=1; for(int i=head[x];i;i=next[i]){ int y=ver[i]; if(v[y]) continue; d[y]=d[x]+1; dfs(y); } }
其實就多了一句話
找到一個點,其全部的子樹中最大的子樹節點數最少,那麼這個點就是這棵樹的重心。
至於怎麼找重心,其實一遍 \(dfs\) 就能夠了。
當遍歷到一個節點時,它的子節點的大小在回溯時能夠直接統計,
關鍵是怎麼求以它爲根,向上發展的一顆子樹,而後你會發現那就是 \(n-size[x]\) (其中 \(n\) 爲總結點數)
代碼以下:
void dfs(int x){ v[x]=1;size[x]=1; for(int i=head[x];i;i=next[i]){ int y=ver[i]; if(v[y]) continue; dfs(y); size[x]+=size[y]; max_part=max(max_part,size[y]); } max_part=max(max_part,n-size[x]); if(max_part<ans){ ans=max_part; pos=x; } }
很簡單的,直接上代碼:
void dfs(int x){ v[x]=cnt; for(int i=head[x];i;i=next[i]){ int y=ver[i]; if(v[y]) continue; dfs(y); } } int main(){ for(int i=1;i<=n;i++) if(!v[i]){ cnt++; dfs(i); } }
很簡單(請不要認爲一下都很簡單,只要你不是神犇,越往下看你會越自閉)
void bfs(){ memset(d,0,sizeof(d)); queue<int>q; q.push(1);d[1]=1; while(!q.empty()){ int x=q.front();q.pop(); for(int i=head[x];i;i=next[i]){ int y=ver[i]; if(d[y]) continue; d[y]=d[x]+1; q.push(y); } } }
BFS是一種按照層次順序遍歷的算法,它具備如下兩個重要性質:
和 DFS 同樣,時間爲 \(O(N+M)\) 。
在圖論中,拓撲排序(Topological Sorting)是一個有向無環圖(DAG, Directed Acyclic Graph)的全部頂點的線性序列。且該序列必須知足下面兩個條件:
每一個頂點出現且只出現一次。
若存在一條從頂點 A 到頂點 B 的路徑,那麼在序列中頂點 A 出如今頂點 B 的前面。
有向無環圖(DAG)纔有拓撲排序,非DAG圖沒有拓撲排序一說。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<queue> #define maxn 10010 #define maxm 100010 using namespace std; int n,m,fa[maxn],sum=0; int head[maxm],cnt=0; struct node{ int to,next; }edge[maxm]; queue<int>q; void addedge(int x,int y){ cnt++; edge[cnt].next=head[x]; edge[cnt].to=y; head[x]=cnt; } void topo(){ for(int i=1;i<=n;i++){ if(!fa[i]) q.push(i);//預處理入度爲0的點入隊 } while(!q.empty()){ int now=q.front(); q.pop(); sum++; printf("%d\n",now); for(int i=head[now];i;i=edge[i].next){ if(!--fa[edge[i].to]) q.push(edge[i].to); } } if(sum<n) printf("false\n"); return; } int main(){ scanf("%d %d",&n,&m); int a,b; for(int i=1;i<=m;i++){ scanf("%d %d",&a,&b); fa[b]++; addedge(a,b); } topo(); return 0; }
具體見書P95。
給定一個N個點,M條邊的有向無環圖,問每一個點直接或間接可到達的點的數量。
書中有詳細介紹,這裏就再也不贅述了,簡而言之就是 拓撲排序+狀態壓縮 。
#include<cstdio> #include<algorithm> #include<iostream> #include<cstring> #include<cmath> #include<bitset> #include<vector> #include<queue> #define N 30030 #define M 30030 using namespace std; int n,m,f[N],side[N]; int head[N],cnt=0; struct node{ int to,next; }edge[M]; vector<int>path; bitset<M>b[N]; queue<int>q; void addedge(int x,int y){ cnt++; edge[cnt].next=head[x]; edge[cnt].to=y; head[x]=cnt; return; } void topo(){ for(int i=1;i<=n;i++){ if(!side[i]) q.push(i); } while(!q.empty()){ int now=q.front(); q.pop(); path.push_back(now); for(int i=head[now];i;i=edge[i].next){ int y=edge[i].to; if(!--side[y]) q.push(y); } } return; } int main(){ memset(side,0,sizeof(side)); scanf("%d %d",&n,&m); int u,v; for(int i=1;i<=m;i++){ scanf("%d %d",&u,&v); addedge(u,v); side[v]++; } topo(); for(int i=path.size()-1;i>=0;i--){ int u=path[i]; b[u][u]=1; for(int j=head[u];j;j=edge[j].next){ int v=edge[j].to; b[u]|=b[v]; } } for(int i=1;i<=n;i++){ printf("%d\n",b[i].count()); } return 0; }
其搜索方式爲,在某個狀態A,找到一個能夠由此狀態轉移到的狀態B,而後轉移到狀態B,遍歷完B全部的後續狀態後,返回狀態A,再尋找A的另外一個可轉移狀態C,如此往復,直至全部狀態被遍歷完。
更通俗地說,即在遍歷時,優先選擇一條路,往搜索樹的深層遍歷,當不能繼續深刻時則返回上一層,選擇另外一個岔路遍歷。
代碼框架大概是這個樣子:
void dfs(...){//搜索狀態 if(...) return;//終止條件及剪枝 for(...){//遍歷子節點 ...//進入下一層以前的處理 dfs(...)//進入下一層 ...//回溯 } return;//結束 }
由於基本知識就這麼多,主要依靠例題來說解。
主要思路書上講的很詳細了,這裏提一下兩個剪枝:
代碼以下:
#include<cstdio> #include<algorithm> #include<iostream> #include<cstring> #include<cmath> #include<queue> #include<vector> #define N 25 #define maxd 999999999 using namespace std; int n,c[N],m; int cab[N],ans=maxd; void dfs(int now,int cnt){ if(now>n){ ans=min(ans,cnt); return; } if(cnt>=ans) return;//剪枝1 for(int i=1;i<=cnt;i++){ if(c[now]+cab[i]<=m){ cab[i]+=c[now]; dfs(now+1,cnt); cab[i]-=c[now]; } } cab[cnt+1]=c[now]; dfs(now+1,cnt+1); cab[cnt+1]=0; return; } bool cmp(int a,int b){ return a>b; } int main(){ scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&c[i]); sort(c+1,c+n+1,cmp);//剪枝2 dfs(1,0); printf("%d\n",ans); //system("pasue"); return 0; }
具體思路一樣被講的很詳細了,這裏一樣再也不贅述。(書上講的不少優化我沒有用到,但仍是能過的)
代碼見下:
#include<cstdio> #include<cstdlib> #include<algorithm> #include<iostream> #include<cstring> using namespace std; int a[20][20]; bool h[20][20],l[20][20],g[20][20]; bool flag; int find(int x,int y){ return (x-1)/3*3+(y-1)/3+1; } void print(){ for(int i=1;i<=9;i++){ for(int j=1;j<=9;j++){ printf("%d",a[i][j]); } printf("\n"); } flag=true; } void dfs(int x,int y){ if(flag) return; if(a[x][y]){ if(x==9 && y==9) print(); if(y==9) dfs(x+1,1); else dfs(x,y+1); } else{ for(int i=1;i<=9;i++){ if(h[x][i]&&l[y][i]&&g[find(x,y)][i]){ a[x][y]=i; h[x][i]=l[y][i]=g[find(x,y)][i]=false; if(x==9&&y==9) print(); if(y==9) dfs(x+1,1); else dfs(x,y+1); a[x][y]=0; h[x][i]=l[y][i]=g[find(x,y)][i]=true; } } } return; } int main(){ int T; scanf("%d",&T); while(T--){ flag=false; memset(h,true,sizeof(h)); memset(l,true,sizeof(l)); memset(g,true,sizeof(g)); for(int i=1;i<=9;i++){ for(int j=1;j<=9;j++){ scanf("%1d",&a[i][j]); if(a[i][j]==0) continue; h[i][a[i][j]]=false; l[j][a[i][j]]=false; g[find(i,j)][a[i][j]]=false; } } dfs(1,1); } return 0; }
搜索的靈魂,暴力的核心(只要剪枝寫得好,直接暴力踩標算)
剪枝:縮小搜索規模以達到時間上的優化,因相似於剪掉搜索樹的枝條,因此形象地稱爲剪枝。
常見的剪枝方法以下:
接下來讓咱們用幾道例題來感覺剪枝的妙用。
具體剪枝思路看書。
代碼以下:
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> using namespace std; int n,cnt,len,a[70]; bool use[70]; bool dfs(int stick,int cab,int last){ if(stick>cnt) return true; if(cab==len) return dfs(stick+1,0,1); int fail=0; for(int i=last;i<=n;i++){//剪枝1 if(!use[i] && cab+a[i]<=len && a[i]!=fail){ use[i]=true; if(dfs(stick,cab+a[i],i+1)) return true; fail=a[i];//剪枝2 use[i]=false; if(cab+a[i]==len || cab==0) return false;//剪枝三、4 } } return false; } bool cmp(int a,int b){ return a>b; } int main(){ int o,val=0,sum=0; scanf("%d",&o); for(int i=1;i<=o;i++){ int p; scanf("%d",&p); if(p<=50){ a[++n]=p; sum+=p; val=max(val,p); } } sort(a+1,a+n+1,cmp);//優化搜索順序 for(len=val;len<=sum;len++){ if(sum%len) continue; memset(use,false,sizeof(use)); cnt=sum/len; if(dfs(1,0,1)) break; } printf("%d\n",len); return 0; }
搜索鼻祖,GY說:「你會了生日蛋糕,你就會了剪枝。」
說了不少次的話不想重複。
#include<cstdio> #include<algorithm> #include<iostream> #include<cstring> #include<cmath> #define maxn 2147483647 using namespace std; int n,m,mins[20],minv[20],ans=maxn; void dfs(int s,int v,int step,int r,int h){ if(step==0){ if(v==n) ans=min(ans,s); return; } //possible剪枝 if(s+mins[step-1]>ans) return; if(v+minv[step-1]>n) return; //best剪枝 if(s+2*(n-v)/r>=ans) return; //next for(int i=r-1;i>=step;i--){ if(m==step) s=i*i; int maxh=min(h-1,(n-v-minv[step-1])/(i*i)); for(int j=maxh;j>=step;j--){ dfs(s+2*i*j,v+i*i*j,step-1,i,j); } } return; } int main(){ scanf("%d %d",&n,&m); for(int i=1;i<=m;i++){ mins[i]=mins[i-1]+2*i*i;//預處理最小面積 minv[i]=minv[i-1]+i*i*i;//預處理最小體積 } dfs(0,0,m,n+1,n+1); if(ans==maxn){ printf("0\n"); return 0; } printf("%d\n",ans); return 0; }
DFS有一個缺陷,就是每次必須選定一個分支,直到終點纔回溯。
假設每一個節點的分支很是多,答案在很淺的節點上,而一開始就選錯了分支,你就GG了。
因此就有了迭代加深(ID)算法,具體流程以下:
迭代加深以dfs爲基礎,本質上仍然是dfs。
從1開始,從小到大枚舉一個深度界限d。
枚舉d的同時進行dfs,並規定這次dfs的深度不能超過d。
當dfs搜索到目標時中止,此時的d值就是可以搜索到答案的最小深度。
你可能會很奇怪,每一次迭代不都重複計算了不少節點嗎?這不會T嗎?
答案是,即便重複搜索,每層的節點都是指數級增加,即便重複搜索 \[1\]~\[d-1\] ,其時間消耗可能還不及第 \(d\) 層。
因此不用擔憂重複的問題。
總而言之:當搜索樹規模隨着層數的深刻而增加很快,並且答案在一個較淺層的節點,就能夠用ID算法。
好像是模板題,本身理解吧:
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #define maxn 20020 using namespace std; int n,m,a[maxn],p[maxn],d; bool f[maxn]; bool chck(){ memset(f,false,sizeof(f)); f[0]=true; for(int i=1;i<=d;i++){ for(int j=0;j+p[i]<=m;j++){ if(f[j]) f[j+p[i]]=true; } } return f[m]; } bool dfs(int x,int y){ if(x>d) return (chck()); for(int i=y;i<=n;i++){ p[x]=a[i]; if(dfs(x+1,i+1)) return true; } return false; } int main(){ scanf("%d %d",&m,&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+n+1); for(d=1;;d++){ if(dfs(1,1)) break; } printf("%d ",d); for(int i=1;i<=d;i++) printf("%d ",p[i]); return 0; }
簡單說就是從初態和終態出發各搜索通常狀態,產生兩棵深度減半的搜索樹,在中間交匯,組成最終答案。
有效地避免了層數過深時的大規模增加,但前提是已知目標狀態。
其實很好寫,例題就不在贅述了。
在以前就已經講述了圖的BFS遍歷,若是咱們把搜索樹當作一張圖,在遍歷時順便作一些處理,這就變成了BFS。
很簡單的介紹,主要經過例題來增強認識吧。
這是一道經典的 走地圖 類的問題,這類問題能夠用BFS來解決(具體見書)
由於書上有標程,並且還寫得挺好,這裏皆採用書上方法實現:
#include<cstdio> #include<algorithm> #include<iostream> #include<cmath> #include<queue> #define maxn 550 using namespace std; struct node{ int x,y,lin; }st,ed; int n,m,f[maxn][maxn][5];//0直立,1橫放(左爲x,y),2豎放(上爲x,y) char s[maxn][maxn]; int dx[5]={0,0,-1,1};//左右上下 int dy[5]={-1,1,0,0}; int next_x[3][4]={{0,0,-2,1},{0,0,-1,1},{0,0,-1,2}}; int next_y[3][4]={{-2,1,0,0},{-1,2,0,0},{-1,1,0,0}}; int next_lin[3][4]={{1,1,2,2},{0,0,1,1},{2,2,0,0}}; queue<node>q; bool in(int x,int y){ return x>=1&&x<=n&&y>=1&&y<=m; } void pre_work(){ for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(s[i][j]=='O'){ed.x=i;ed.y=j;ed.lin=0;s[i][j]='.';} else if(s[i][j]=='X'){ //bool flag=false; for(int k=0;k<=3;k++){ int x=i+dx[k]; int y=j+dy[k]; if(in(x,y)&&s[x][y]=='X'){ //flag=true; st.x=min(i,x);st.y=min(j,y); st.lin=k<2?1:2; s[i][j]=s[x][y]='.'; break; } } if(s[i][j]=='X'){st.x=i;st.y=j;st.lin=0;} } return; } bool able(node next){ if(!in(next.x,next.y)) return false; if(s[next.x][next.y]=='#') return false; if(next.lin==0&&s[next.x][next.y]!='.') return false; if(next.lin==1&&s[next.x][next.y+1]=='#') return false; if(next.lin==2&&s[next.x+1][next.y]=='#') return false; return true; } int bfs(){ while(!q.empty()) q.pop(); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ for(int k=0;k<=2;k++) f[i][j][k]=-1; } } f[st.x][st.y][st.lin]=0; q.push(st); while(!q.empty()){ node now=q.front();q.pop(); for(int i=0;i<=3;i++){ node next; next.x=now.x+next_x[now.lin][i]; next.y=now.y+next_y[now.lin][i]; next.lin=next_lin[now.lin][i]; if(!able(next)) continue; if(f[next.x][next.y][next.lin]==-1){ f[next.x][next.y][next.lin]=f[now.x][now.y][now.lin]+1; q.push(next); if(ed.x==next.x&&ed.y==next.y&&ed.lin==next.lin) return f[next.x][next.y][next.lin]; } } } return -1; } int main(){ while(~scanf("%d %d",&n,&m)&&n){ for(int i=1;i<=n;i++) scanf("%s",s[i]+1); pre_work(); //printf("%d %d %d\n%d %d %d\n",st.x,st.y,st.lin,ed.x,ed.y,ed.lin); int ans=bfs(); if(ans==-1) printf("Impossible\n");else printf("%d\n",ans); } return 0; }
好像碼量還挺大
一樣是 走地圖 式的題目,可是好惡心!(思路書上有)
#include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #include<iostream> #include<string> #define maxn 25 using namespace std; int n,m,px,py,bx,by; char a[maxn][maxn]; int dx[5]={1,-1,0,0}; int dy[5]={0,0,1,-1}; char box_path[5]="SNEW"; char man_path[5]="snew"; bool box_vis[maxn][maxn]; bool man_vis[maxn][maxn]; string pathman; struct man{ int x,y; string path; }; struct box{ int x,y; int mx,my; string path; }; bool in(int x,int y){ return x>=1&&x<=n&&y>=1&&y<=m; } bool bfs_man(int cantx,int canty,int sx,int sy,int ex,int ey){ memset(man_vis,0,sizeof(man_vis)); man p; pathman=""; if(ex<1||ex>n||ey<1||ey>m) return false; p.x=sx;p.y=sy;p.path=""; man_vis[sx][sy]=true; queue<man>q; q.push(p); while(!q.empty()){ man now=q.front(); q.pop(); if(now.x==ex&&now.y==ey){ pathman=now.path; return true; } for(int i=0;i<=3;i++){ int gx=now.x+dx[i]; int gy=now.y+dy[i]; if(gx==cantx && gy==canty) continue; if(in(gx,gy)&&!man_vis[gx][gy]&&a[gx][gy]!='#'){ man_vis[gx][gy]=true; p.x=gx;p.y=gy; p.path=now.path+man_path[i]; q.push(p); } } } return false; } void bfs_box(){ memset(box_vis,0,sizeof(box_vis)); queue<box>q; box p; p.x=bx; p.y=by; p.mx=px; p.my=py; p.path=""; box_vis[bx][by]=true; q.push(p); while(!q.empty()){ box now=q.front(); q.pop(); for(int i=0;i<=3;i++){ int gx=now.x+dx[i]; int gy=now.y+dy[i]; if(in(gx,gy)&&a[gx][gy]!='#'&&!box_vis[gx][gy]&& bfs_man(now.x,now.y,now.mx,now.my,now.x-dx[i],now.y-dy[i])){ box_vis[gx][gy]=true; p.x=gx;p.y=gy; p.mx=now.x;p.my=now.y; p.path=now.path+pathman+box_path[i]; if(a[gx][gy]=='T'){ cout << p.path << endl; return; } q.push(p); } } } printf("Impossible.\n"); return; } int main(){ int cnt=0; while(~scanf("%d %d",&n,&m)&&n){ for(int i=1;i<=n;i++){ scanf("%s",a[i]+1); for(int j=1;j<=m;j++){ if(a[i][j]=='S'){ px=i;py=j; } else if(a[i][j]=='B'){ bx=i;by=j; } } } printf("Maze #%d\n",++ cnt); bfs_box(); printf("\n"); } return 0; }
在以上的BFS算法之中,每走一步的代價始終是 \(1\) ,但若是不只僅是 \(1\) 咱們還算得出來嗎?
別急,先看邊權是 \(0\) 或 \(1\) ,的狀況。
其實很簡單的啦,在一張邊權要麼是0,要麼是1的無向圖上,咱們能夠經過雙端隊列BFS來實現搜索。
大致和基礎廣搜相似,只是若是一味往隊尾添加元素的話,沒法知足單調性(1有可能在0的前面)
因此當邊權是0時,插入隊頭,不然插入隊位,而後就和正常廣搜同樣了。(不懂 \(deque\) 請自行百度)
代碼以下:
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<deque> #define N 550 #define M 3000000 using namespace std; int n,m,T,f[N*N]; char a[N][N]; int head[N*N],cnt=0; struct node{ int to,next,val; }edge[M]; deque<int>q; int get_num(int x,int y){ return x*(m+1)+y+1; } void addedge(int x,int y,int z){ cnt++; edge[cnt].next=head[x]; edge[cnt].to=y; edge[cnt].val=z; head[x]=cnt; return; } void bfs(){ while(!q.empty()) q.pop_front(); for(int i=0;i<=(n+1)*(m+1);i++) f[i]=-1; f[1]=0; q.push_front(1); while(!q.empty()){ int now=q.front(); q.pop_front(); if(now==(n+1)*(m+1)){ printf("%d\n",f[now]); return; } for(int i=head[now];i;i=edge[i].next){ int y=edge[i].to; int z=edge[i].val; if(f[y]==-1||f[y]>f[now]+z){ f[y]=f[now]+z; if(z) q.push_back(y); else q.push_front(y); } } } printf("NO SOLUTION\n"); return; } int main(){ scanf("%d",&T); while(T--){ scanf("%d %d",&n,&m); cnt=0; memset(head,0,sizeof(head)); for(int i=1;i<=n;i++){ scanf("%s",a[N]+1); for(int j=1;j<=m;j++){ if(a[i][j]=='/'){ addedge(get_num(i-1,j-1),get_num(i,j),1);//氣 addedge(get_num(i,j),get_num(i-1,j-1),1);//勢 addedge(get_num(i-1,j),get_num(i,j-1),0);//磅 addedge(get_num(i,j-1),get_num(i-1,j),0);//礴 } else{ addedge(get_num(i-1,j-1),get_num(i,j),0);//的 addedge(get_num(i,j),get_num(i-1,j-1),0);//存 addedge(get_num(i-1,j),get_num(i,j-1),1);//圖 addedge(get_num(i,j-1),get_num(i-1,j),1);//啊(這個是湊字用的) } } } bfs(); } }
對於更具備普適性的算法,邊權爲任意值怎麼作呢?
方法一:當有負權時
方法二:當無負權時
That's so easy that I don't want to say any more.
其實沒什麼兩樣,就是兩邊輪流進行,每次擴展一層。
很簡單的雙向廣搜題,但要注意男孩走三層,女孩走一層。(就被這個坑了 \(INF\) 次)
代碼以下:
#include<cstdio> #include<algorithm> #include<iostream> #include<cstring> #include<cmath> #include<queue> #define maxn 810 using namespace std; int n,m,f[maxn][maxn]; int dx[5]={0,1,-1,0,0}; int dy[5]={0,0,0,1,-1}; bool vis[maxn][maxn][2]; char s[maxn][maxn]; struct point{ int x,y; }; point mm,gg,go[2]; queue<point>q[2]; void init(){ int t=0; scanf("%d %d",&n,&m); for(int i=1;i<=n;i++){ scanf("%s",s[i]+1); for(int j=1;j<=m;j++){ if(s[i][j]=='M'){mm.x=i;mm.y=j;} else if(s[i][j]=='G'){gg.x=i;gg.y=j;} else if(s[i][j]=='Z'){go[t].x=i;go[t++].y=j;} } } return; } void pre_work(){ while(!q[0].empty()) q[0].pop(); while(!q[1].empty()) q[1].pop(); memset(vis,false,sizeof(vis)); return; } int dis(int x1,int y1,int x2,int y2){ return abs(x1-x2)+abs(y1-y2); } bool chck(int x,int y,int typ,int step){ if(x<1 || x>n || y<1 || y>m || s[x][y]=='X') return false; for(int i=0;i<=1;i++){ if(dis(x,y,go[i].x,go[i].y)<=step*2) return false; } return true; } bool bfs(int typ,int step){ int si=q[typ].size(); while(si--){ point now=q[typ].front(); q[typ].pop(); if(!chck(now.x,now.y,typ,step)) continue; for(int i=1;i<=4;i++){ int fx=now.x+dx[i]; int fy=now.y+dy[i]; if(!chck(fx,fy,typ,step)||vis[fx][fy][typ]) continue; if(vis[fx][fy][typ^1]) return true; vis[fx][fy][typ]=true; q[typ].push((point){fx,fy}); } } return false; } void work(){ pre_work(); vis[mm.x][mm.y][0]=true; vis[gg.x][gg.y][1]=true; q[0].push((point){mm.x,mm.y}); q[1].push((point){gg.x,gg.y}); int step=0; while(!q[0].empty() || !q[1].empty()){ step++; for(int i=1;i<=3;i++){ if(bfs(0,step)){ printf("%d\n",step); return; } } if(bfs(1,step)){ printf("%d\n",step); return; } } printf("-1\n"); return; } int main(){ int T; scanf("%d",&T); while(T--){ init(); work(); } return 0; }
難度真心很大,在優先隊列BFS算法,若是給定目標狀態要求求出到達目標狀態的最小代價,顯然它不夠優。
由於它是創建在貪心的基礎之上的,但目前最優不表明之後最優。
因此爲了提升搜索效率,咱們能夠設置一個估價函數,計算出全部已知狀態的估計值,
不斷從堆頂出選出 當前代價+將來估價 最小的狀態進行擴展。
注意,估價函數必定要知足如下準則 (核能預警) :
設估價函數值爲 $ f(state)$
設將來實際求出的最小值爲 $ g(state)$
必定要知足 \(f(state)<=d(state)\)
這個規律顯而易見吧。
這種**帶有估價函數的優先隊列BFS就稱爲A*算法**。
思路很巧~~(好像A*的思路都很巧)~~
簡單說就是講終點到這個節點的最短路做爲估價函數(顯然最短路<第K短路)
代碼以下:
#include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<iostream> #include<queue> #include<vector> #define N 1010 #define M 100010 using namespace std; int n,m,s,t,k; int dis[N],head[N],sum[N],cnt=0; bool use[N]; struct node{ int to,val,next; }edge[M]; priority_queue<pair<int,int> >q; struct Astar{ int x,h,g; friend bool operator < (Astar a,Astar b){ return a.h+a.g>b.h+b.g; } }; priority_queue<Astar>qq; vector<node>v[N]; void addedge(int x,int y,int z){ cnt++; edge[cnt].next=head[x]; edge[cnt].to=y; edge[cnt].val=z; head[x]=cnt; v[y].push_back((node){x,z,0}); return; } void dij(){ memset(use,false,sizeof(use)); memset(dis,0x3f,sizeof(dis)); dis[t]=0; q.push(make_pair(0,t)); while(!q.empty()){ int now=q.top().second; q.pop(); if(use[now]) continue; use[now]=true; for(int i=0;i<v[now].size();i++){ int y=v[now][i].to; int z=v[now][i].val; if(dis[y]>dis[now]+z){ dis[y]=dis[now]+z; q.push(make_pair(-dis[y],y)); } } } return; } void A_star(){ memset(sum,0,sizeof(sum)); qq.push((Astar){s,0,0}); while(!qq.empty()){ Astar now=qq.top(); qq.pop(); sum[now.x]++; if(sum[now.x]==k && now.x==t){ printf("%d\n",now.h); return; } if(sum[now.x]>k) continue; for(int i=head[now.x];i;i=edge[i].next){ int y=edge[i].to; int z=edge[i].val; //printf("%d %d\n",y,dis[y]+now.dist+z); qq.push((Astar){y,now.h+z,dis[y]}); } } printf("-1\n"); return; } int main(){ scanf("%d %d",&n,&m); int a,b,c; for(int i=1;i<=m;i++){ scanf("%d %d %d",&a,&b,&c); addedge(a,b,c); } scanf("%d %d %d",&s,&t,&k); dij(); if(s==t) k++; A_star(); return 0; }
我好像是用雙向BFS作的,可是A*跑得更快(本身啃書吧)
#include<algorithm> #include<cstdio> #include<queue> #include<iostream> #include<cmath> #include<map> using namespace std; int a,b=123804765,p[5][5],fx,fy; int dx[5]={0,0,0,1,-1}; int dy[5]={0,1,-1,0,0}; queue<int>q; map<int,int>flag; map<int,int>ans; void To_juzhen(int now){ for(int i=3;i>=1;i--){ for(int j=3;j>=1;j--){ p[i][j]=now%10;now/=10; if(p[i][j]==0){ fx=i;fy=j; } } } return; } int To_shulie(){ int x=0; for(int i=1;i<=3;i++){ for(int j=1;j<=3;j++){ x=x*10+p[i][j]; } } return x; } void bfs(){ q.push(a);q.push(b); flag[a]=1;flag[b]=2; ans[a]=ans[b]=1; while(!q.empty()){ int now,cur=q.front(); now=cur; q.pop(); To_juzhen(now); for(int i=1;i<=4;i++){ int gx=fx+dx[i]; int gy=fy+dy[i]; if(gx>3||gx<1||gy>3||gy<1) continue; swap(p[fx][fy],p[gx][gy]); now=To_shulie(); if(flag[now]==flag[cur]){ swap(p[fx][fy],p[gx][gy]); continue; } if(flag[now]+flag[cur]==3){ printf("%d\n",ans[now]+ans[cur]-1); return; } flag[now]=flag[cur]; ans[now]=ans[cur]+1; q.push(now); swap(p[fx][fy],p[gx][gy]); } } return; } int main(){ scanf("%d",&a); if(a==b){ printf("0\n"); return 0; } bfs(); return 0; }
簡單說,\[ IDA*=ID+A* \] (廢話)
核心一句話:若當前深度+將來估計步數>深度限制,當即回溯 。
簡單的很,具體思路回原文。
#include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<iostream> #define maxn 20 using namespace std; int n,a[maxn],flag,deep; void book_swap(int x,int y,int z){ int p[maxn],tot=x; for(int i=y+1;i<=z;i++) p[tot++]=a[i]; for(int i=x;i<=y;i++) p[tot++]=a[i]; for(int i=x;i<=z;i++) a[i]=p[i]; return; } int h(){ int sum=0; for(int i=1;i<n;i++){ if(a[i+1]!=a[i]+1) sum++; } if(a[n]!=n) sum++; return ceil(((double)sum/3)); } void dfs(int step){ if(step+h()>deep||flag) return; if(h()==0){ flag=true; printf("%d\n",step); return; } for(int i=1;i<n;i++){ for(int j=i;j<n;j++){ for(int k=j+1;k<=n;k++){ book_swap(i,j,k); dfs(step+1); if(flag) return; book_swap(i,i+k-j-1,k); } } } return; } void IDA(){ deep=0; flag=false; while(1){ deep++; dfs(0); if(flag) break; if(deep==4){ printf("5 or more\n"); break; } } return; } int main(){ int T; scanf("%d",&T); while(T--){ scanf("%d",&n); memset(a,0,sizeof(a)); for(int i=1;i<=n;i++) scanf("%d",&a[i]); IDA(); } return 0; }
DFS -> 剪枝 -> ID、雙向 -> IDA*
BFS -> 雙端隊列、優先隊列、雙向 -> A*
搜索題主要考驗代碼能力,也是加強代碼能力的好方法,但在 \(noip\) 以上的比賽中,並非主要考點...
學好搜索,相信你能夠 暴力踩標算,打表出省一 。
#include<algorithm> #include<cstdio> #include<queue> #include<iostream> #include<cmath> #include<map> using namespace std; int a,b=123804765,p[5][5],fx,fy; int dx[5]={0,0,0,1,-1}; int dy[5]={0,1,-1,0,0}; queue<int>q; map<int,int>flag; map<int,int>ans; void To_juzhen(int now){ for(int i=3;i>=1;i--){ for(int j=3;j>=1;j--){ p[i][j]=now%10;now/=10; if(p[i][j]==0){ fx=i;fy=j; } } } return; } int To_shulie(){ int x=0; for(int i=1;i<=3;i++){ for(int j=1;j<=3;j++){ x=x*10+p[i][j]; } } return x; } void bfs(){ q.push(a);q.push(b); flag[a]=1;flag[b]=2; ans[a]=ans[b]=1; while(!q.empty()){ int now,cur=q.front(); now=cur; q.pop(); To_juzhen(now); for(int i=1;i<=4;i++){ int gx=fx+dx[i]; int gy=fy+dy[i]; if(gx>3||gx<1||gy>3||gy<1) continue; swap(p[fx][fy],p[gx][gy]); now=To_shulie(); if(flag[now]==flag[cur]){ swap(p[fx][fy],p[gx][gy]); continue; } if(flag[now]+flag[cur]==3){ printf("%d\n",ans[now]+ans[cur]-1); return; } flag[now]=flag[cur]; ans[now]=ans[cur]+1; q.push(now); swap(p[fx][fy],p[gx][gy]); } } return; } int main(){ scanf("%d",&a); if(a==b){ printf("0\n"); return 0; } bfs(); return 0; }
#include<algorithm> #include<cstdio> #include<iostream> #include<cstring> #include<cmath> using namespace std; int n,a[30],b[30],c[30]; int num[30],id[30],sum=0; char aa[30],bb[30],cc[30]; bool use[30]; void Get(int x){ if(!use[x]){ use[x]=true; id[sum++]=x; } return; } bool check(){ for(int i=n,p=0;i>=1;i--){ int A=num[a[i]],B=num[b[i]],C=num[c[i]]; if((A+B+p)%n!=C) return false; p=(A+B+p)/n; } return true; } void print(){ for(int i=1;i<=n;i++) printf("%d ",num[i]); exit(0); } bool cut_down(){ if(num[a[1]]+num[b[1]]>=n) return true; for(int i=n;i>=1;i--){ int A=num[a[i]],B=num[b[i]],C=num[c[i]]; if(A==-1||B==-1||C==-1) continue; if((A+B)%n!=C&&(A+B+1)%n!=C) return true; } return false; } void dfs(int x){ //for(int i=1;i<=n;i++) printf("%d ",num[i]); //printf("\n"); if(cut_down()) return; if(x==n){ if(check()) print(); return; } for(int i=n-1;i>=0;i--){ if(!use[i]){ num[id[x]]=i; use[i]=true; dfs(x+1); num[id[x]]=-1; use[i]=false; } } return; } int main(){ scanf("%d",&n); cin>>aa>>bb>>cc; for(int i=1;i<=n;i++){ a[i]=aa[i-1]-'A'+1; b[i]=bb[i-1]-'A'+1; c[i]=cc[i-1]-'A'+1; num[i]=-1; } memset(use,false,sizeof(use)); for(int i=n;i>=1;i--){ Get(a[i]);Get(b[i]);Get(c[i]); } memset(use,false,sizeof(use)); dfs(0); return 0; }
#include<cstdio> #include<algorithm> #include<iostream> #include<cmath> #include<cstring> #include<cstdlib> using namespace std; int deep,map[10][10],ans[10][5]; int last[10][10][10]; void copy(int x){ for(int i=1;i<=5;i++){ for(int j=1;j<=7;j++){ last[x][i][j]=map[i][j]; } } return; } void build(){ for(int i=1;i<=5;i++){ int sum=0; for(int j=1;j<=7;j++){ if(!map[i][j]) sum++; else{ if(!sum) continue; map[i][j-sum]=map[i][j]; map[i][j]=0; } } } return; } bool can[10][10]; bool delet(){ bool flag=false; for(int i=1;i<=5;i++){ for(int j=1;j<=7;j++){ if(i-1>=1&&i+1<=5&&map[i][j]==map[i-1][j]&&map[i][j]==map[i+1][j]&&map[i][j]){ can[i][j]=true;can[i-1][j]=true;can[i+1][j]=true;flag=true; } if(j-1>=1&&j+1<=7&&map[i][j]==map[i][j+1]&&map[i][j]==map[i][j-1]&&map[i][j]){ can[i][j]=true;can[i][j-1]=true;can[i][j+1]=true;flag=true; } } } if(!flag) return false; for(int i=1;i<=5;i++){ for(int j=1;j<=7;j++){ if(can[i][j]){ map[i][j]=0; can[i][j]=false; } } } return true; } void change(int i,int j,int x){ int tmp=map[i][j]; map[i][j]=map[i+x][j]; map[i+x][j]=tmp; build(); while(delet())build();//我就把while寫成if了,調了我三個小時... } bool chck(){ for(int i=1;i<=5;i++){ if(map[i][1]) return false; } return true; } void dfs(int x){ if(chck()){ for(int i=1;i<=deep;i++){ if(i!=1)printf("\n"); for(int j=1;j<=3;j++) printf("%d ",ans[i][j]); } exit(0); } if(x==deep+1)return; copy(x); for(int i=1;i<=5;i++) for(int j=1;j<=7;j++){ if(map[i][j]){ if(i+1<=5&&map[i][j]!=map[i+1][j]){ change(i,j,1); ans[x][1]=i-1;ans[x][2]=j-1;ans[x][3]=1; dfs(x+1); for(int i=1;i<=5;i++) for(int j=1;j<=7;j++) map[i][j]=last[x][i][j]; ans[x][1]=-1;ans[x][2]=-1;ans[x][3]=-1; } if(i-1>=1&&map[i-1][j]==0){ change(i,j,-1); ans[x][1]=i-1;ans[x][2]=j-1;ans[x][3]=-1; dfs(x+1); for(int i=1;i<=5;i++) for(int j=1;j<=7;j++) map[i][j]=last[x][i][j]; ans[x][1]=-1;ans[x][2]=-1;ans[x][3]=-1; } } } } int main(){ int p; scanf("%d",&deep); for(int i=1;i<=5;i++){ for(int j=1;j<=8;j++){ scanf("%d",&p); if(!p) break; map[i][j]=p; } } memset(ans,-1,sizeof(ans)); dfs(1); printf("-1\n"); return 0; }
#include<cstdio> #include<algorithm> #include<iostream> #include<cstring> #include<queue> #define maxn 160 using namespace std; int n,m,sx,sy,ex,ey; int dx[10]={0,2,2,1,1,-1,-1,-2,-2}; int dy[10]={0,1,-1,2,-2,2,-2,1,-1}; int dis[maxn][maxn]; char a[maxn][maxn]; struct node{ int x,y; }; queue<node>q; void bfs(){ q.push((node){sx,sy}); memset(dis,-1,sizeof(dis)); dis[sx][sy]=0; while(!q.empty()){ node now=q.front(); q.pop(); for(int i=1;i<=8;i++){ int fx=dx[i]+now.x; int fy=dy[i]+now.y; if(fx<1 || fx>n || fy<1 || fy>m || dis[fx][fy]!=-1 || a[fx][fy]=='*') continue; dis[fx][fy]=dis[now.x][now.y]+1; if(fx==ex && fy==ey){ printf("%d\n",dis[fx][fy]); return; } q.push((node){fx,fy}); } } } int main(){ scanf("%d %d",&m,&n); for(int i=1;i<=n;i++){ scanf("%s",a[i]+1); for(int j=1;j<=m;j++){ if(a[i][j]=='K'){sx=i;sy=j;} else if(a[i][j]=='H'){ex=i;ey=j;} } } bfs(); return 0; }
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<queue> #define maxn 110 using namespace std; int n,m,sx,sy,sum=0,tot=1; int dx[10]={0,1,-1,0,0,1,1,-1,-1}; int dy[10]={0,0,0,1,-1,1,-1,1,-1}; int dis[maxn][maxn]; char a[maxn][maxn]; queue<pair<int,int> >q; void bfs(){ memset(dis,-1,sizeof(dis)); dis[sx][sy]=0; q.push(make_pair(sx,sy)); while(!q.empty()){ int nowx=q.front().first; int nowy=q.front().second; q.pop(); for(int i=1;i<=8;i++){ int fx=nowx+dx[i]; int fy=nowy+dy[i]; if(fx<1 || fx>n || fy<1 || fy>m || dis[fx][fy]!=-1 || a[fx][fy]=='*') continue; dis[fx][fy]=dis[nowx][nowy]+1; tot++; if(tot==sum){ printf("%d\n",dis[fx][fy]); return; } q.push(make_pair(fx,fy)); } } return; } int main(){ int p,q; scanf("%d %d %d %d",&m,&n,&p,&q); sx=n-q+1;sy=p; for(int i=1;i<=n;i++){ scanf("%s",a[i]+1); for(int j=1;j<=m;j++){ if(a[i][j]!='*'){ sum++; } } } //printf("%d\n",sum); bfs(); return 0; }
#include<cstdio> #include<algorithm> #include<cstring> #include<string> #include<map> #include<iostream> #include<cmath> #define maxn 9999999 using namespace std; string a,b,change1[10],change2[10]; map<string,bool>v1; map<string,int>v2; int t=1,d=1,ans=maxn; void dfs(string now,int step){ if(step>d) return; if(now==b){ ans=min(ans,step); return; } if(v1[now]){ if(step>=v2[now]) return; } v1[now]=true;v2[now]=step; int f; string x; for(int i=1;i<=t;i++){ f=-1; while(1){ f=now.find(change1[i],f+1); if(f==-1) break; x=now; x.erase(f,change1[i].size()); x.insert(f,change2[i]); dfs(x,step+1); } } return; } int main(){ cin>>a>>b; while(cin>>change1[t]>>change2[t]) t++; t--; while(ans==maxn){ dfs(a,0); v1.clear();v2.clear(); d++; if(d>10) break; } if(ans==maxn){ printf("NO ANSWER!\n"); return 0; } printf("%d\n",ans); return 0; }
#include<algorithm> #include<cstdio> #include<iostream> #include<cstring> #include<cmath> using namespace std; int T,a[10][10]; bool flag;//,chong[10][10]; int dx[10]={0,1,1,-1,-1,2,2,-2,-2}; int dy[10]={0,2,-2,2,-2,1,-1,1,-1}; int f[10][10]={ {0,0,0,0,0,0}, {0,1,1,1,1,1}, {0,0,1,1,1,1}, {0,0,0,2,1,1}, {0,0,0,0,0,1}, {0,0,0,0,0,0}, }; int check(){ int sum=0; for(int i=1;i<=5;i++){ for(int j=1;j<=5;j++){ if(f[i][j]!=a[i][j]) sum++; } } return sum; } void dfs(int xx,int yy,int step,int maxstep){ if(flag) return; if(step==maxstep){ if(!check()) flag=true; return; } //if(step+check()/2>maxstep) return; for(int i=1;i<=8;i++){ int gx=xx+dx[i]; int gy=yy+dy[i]; if(gx>5||gx<1||gy>5||gy<1) continue; //chong[gx][gy]=true; swap(a[xx][yy],a[gx][gy]); if(check()+step<=maxstep) dfs(gx,gy,step+1,maxstep); swap(a[xx][yy],a[gx][gy]); } return; } int main(){ scanf("%d",&T); while(T--){ char ch; int xx,yy; flag=false; for(int i=1;i<=5;i++){ for(int j=1;j<=5;j++){ cin>>ch; if(ch=='*') {a[i][j]=2;xx=i;yy=j;} else a[i][j]=ch-'0'; } } if(!check()) {printf("0\n");continue;} for(int i=1;i<=15;i++){ //memset(chong,false,sizeof(chong)); dfs(xx,yy,0,i); if(flag) {printf("%d\n",i);break;} } if(!flag) printf("-1\n"); } return 0; }
終於寫完了,好開心。
全篇惟一參考資料:《算法競賽進階指南》
再見。