主页 > 游戏开发  > 

dfs剪枝


1.可行性剪枝 木棒

首先思考搜索的过程:遍历可能的所有长度,dfs判断能否拼出。

在dfs的过程中,如果当前已经拼成的木棒数量等于长度下对应的木棒数量,则返回true,如果无法拼出,则返回false。

接下来开始剪枝。

首先思考搜索顺序:我们应该优先放置长度较长的木棒。

比较关键的剪枝是可行性剪枝,比较难想:

①如果一根木棒放在某个位置不成功,那么和它等长的所有木棒放在该位置都不会成功。

②如果一根木棒放在某根木棍的第一个位置不成功,那么接下来无论它放在哪个位置都不可能成功。因为当它放在第一个位置不成功时,说明后面的所有木棒的组合都无法和它拼出一条木棍,而我们越往后选择木棒,可以选择的木棒只会变少,更不可能和它组合拼出一条木棍了。

③如果一根木棒放在某根木棍的最后一个位置成功,但是接下来的某一根木棍无法拼出,那么接下来它无论放在哪个位置都不可能拼出答案。可以用反证法证明,假设它放在最后一个位置是成功的,我们可以用两根更短的木棒x、y替代当前木棒i,如果可以得出一种成功的方案,在这种方案中,因为i和x+y是等长的,我们一定可以通过交换把i换到某根木棍的最后一个位置,这和已知的一定不成功矛盾了。

#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=70; int a[N]; int n; bool st[N]; bool cmp(int x,int y) { return x>y; } bool dfs(int u,int curlen,int taglen,int tagcnt) { if(curlen==taglen) return dfs(u+1,0,taglen,tagcnt); if(u>=tagcnt) return true; for(int i=1;i<=n;i++) { if(st[i]) continue; if(curlen+a[i]<=taglen) { st[i]=1; if(dfs(u,curlen+a[i],taglen,tagcnt)) return true; st[i]=0; if(curlen+a[i]==taglen) return false; int j=i; while(a[i]==a[j]) j++; i=j-1; if(curlen==0) return false; } } return false; } int main() { while(cin>>n&&n!=0) { int sum=0; memset(st,0,sizeof(st)); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); sum+=a[i]; } sort(a+1,a+1+n,cmp); for(int i=a[1];i<=sum;i++) { if(sum%i==0) { if(dfs(0,0,i,sum/i)) { printf("%d\n",i); break; } } } } } 2.迭代加深 加成序列

170. 加成序列 - AcWing题库

简言之,就是找到一个单调递增的序列,其中最大的数字是n,且每个数字由它及它之前的任意两个数字相加而成。

按照最暴力的方式搜索,我们的树是这样的:

左边的两棵树明显不是最优答案,但是我们一定会将它搜到底,再去搜索其他的方案,我们在完全不可能的子树中浪费了很多时间。

对于这样的情况,我们可以用迭代加深来进行优化。

在每一次搜索时,我们提前设置好本次搜索最多搜到哪一层,如果在当前层搜到了,就不需要往下一层走。

#include<iostream> using namespace std; const int N=110; int n; int path[N]; bool dfs(int u,int depth)//在填path第u个位置上的数,当前最大层数depth { if(path[u-1]==n) return true; if(u>depth) return false; bool st[N]={0}; for(int i=u-1;i>=1;i--) for(int j=i;j>=1;j--) { int t=path[i]+path[j]; if(t>path[u-1]&&u<=n) { path[u]=t; st[u]=1; if(dfs(u+1,depth)) return true; } } return false; } int main() { while(cin>>n&&n!=0) { path[1]=1; int depth=1; while(!dfs(2,depth)) depth++; for(int i=1;i<=depth;i++) printf("%d ",path[i]); printf("\n"); } } 3.双向dfs 送礼物

171. 送礼物 - AcWing题库

这一题乍一眼看是01背包,但是w范围太大了。

考虑dfs,一共有种状态,这里n是46,时间复杂度超了。

这里可以双向dfs,我们先求出前n/2个物品中合理的所有可能(记录在a数组中),再对后n/2个物品进行组合,当组合出的情况合理时,我们找到a数组中加上当前值离w最近的值,也就是说,在a数组中找到小于(w-当前值)的最大值,这个过程可以用二分。最后的时间复杂度就是2**log(n/2)。

#include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int N=50; int w,n,k; int a[N]; int weight[1<<24],cnt=1; ll res=0; void dfs1(int u,ll s) { if(u>k) { weight[cnt++]=s; return; } dfs1(u+1,s); if((ll)s+a[u]<=w) dfs1(u+1,(ll)s+a[u]); } void dfs2(int u,ll s) { if(u>n) { int l=0,r=cnt-1; while(l<r) { int mid=(l+r+1)>>1; if(weight[mid]<=w-s) l=mid; else r=mid-1; } res=max(res,s+weight[l]); return; } dfs2(u+1,s); if((ll)a[u]+s<=w) dfs2(u+1,(ll)s+a[u]); } int main() { scanf("%d%d",&w,&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); k=n/2; dfs1(1,0); cnt=unique(weight,weight+cnt)-weight; sort(weight,weight+cnt); dfs2(k+1,0); printf("%lld",res); } 4.IDA* 排书

180. 排书 - AcWing题库

IDA*其实是可行性剪枝和迭代加深的结合,在迭代加深的基础上,加上一个类似于A*的代价函数就好了。

在本题中,我们可以发现,当我们每次执行插入操作时,会改变三对相邻结点的顺序。

也就是说,每次进行插入操作,我们最多使当前状态向前走三步。于是,我们的代价函数可以是,计算当前距离终点还差几步tot,则至少还需要(tot+2)/3,即(tot/3)向上取整的代价。

#include<iostream> #include<cstring> using namespace std; const int N=20; int n; int a[N]; int w[5][N]; int f() { int tot=0; for(int i=1;i<n;i++) if(a[i+1]!=a[i]+1) tot++; return (tot+2)/3; } bool dfs(int u,int depth) { if(u>depth||f()+u>depth) return false; if(f()==0) return true; for(int len=1;len<=n;len++) for(int l=1;l+len-1<=n;l++) { int r=l+len-1; for(int k=r+1;k<=n;k++) { memcpy(w[u],a,sizeof(a)); int y=l,x; for(x=r+1;x<=k;x++,y++) a[y]=w[u][x]; for(x=l;x<=r;x++,y++) a[y]=w[u][x]; if(dfs(u+1,depth)) return true; memcpy(a,w[u],sizeof(a)); } } return false; } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); int depth=0; while(depth<5&&dfs(0,depth)==0) depth++; if(depth>=5) printf("5 or more\n"); else printf("%d\n",depth); } } 回转游戏

181. 回转游戏 - AcWing题库

因为棋盘的形状比较奇怪,我们可以用一个8*7的数组来记录每一个操作改变的对应行。

int g[N][N]={ {1,3,7,12,16,21,23}, {2,4,9,13,18,22,24}, {11,10,9,8,7,6,5}, {20,19,18,17,16,15,14}, {24,22,18,13,9,4,2}, {23,21,16,12,7,3,1}, {14,15,16,17,18,19,20}, {5,6,7,8,9,10,11} };

我们的代价函数可以是,统计一下当前中心8个数中出现最多的数字出现了几次,则我们还需要的最少代价就是8-最大出现次数。

int f() { int cnt[4]={0}; for(int i=0;i<8;i++) cnt[q[center[i]]]++; sort(cnt,cnt+4); return 8-cnt[3]; }

这题比较困扰我的是数据的处理上,在用数组打表了之后,我们操作时,每一行在棋盘上对应的编号是不变的,但是编号对应的数字需要改变,因此是将某一行的数字进行移动。

也就是说,每次操作是将某一行的q值进行改变,而不是g值。

#include<iostream> #include<vector> #include<algorithm> #include<cstring> using namespace std; const int N=8; int g[N][N]={ {1,3,7,12,16,21,23}, {2,4,9,13,18,22,24}, {11,10,9,8,7,6,5}, {20,19,18,17,16,15,14}, {24,22,18,13,9,4,2}, {23,21,16,12,7,3,1}, {14,15,16,17,18,19,20}, {5,6,7,8,9,10,11} }; int opposite[N]={5,4,7,6,1,0,3,2}; int center[N]={7,8,9,12,13,16,17,18}; int q[3*N+5]; vector<int> path; int f() { int cnt[4]={0}; for(int i=0;i<8;i++) cnt[q[center[i]]]++; sort(cnt,cnt+4); return 8-cnt[3]; } void operate(int x) { int t=q[g[x][0]]; for(int i=0;i<6;i++) q[g[x][i]]=q[g[x][i+1]]; q[g[x][6]]=t; } bool dfs(int u,int depth,int last) { if(u>depth||u+f()>depth) return false; if(f()==0) return true; for(int i=0;i<8;i++) { if(last==opposite[i]) continue; operate(i); path.push_back(i); if(dfs(u+1,depth,i)) return true; operate(opposite[i]); path.pop_back(); } return false; } int main() { while(cin>>q[1]&&q[1]!=0) { for(int i=2;i<=24;i++) cin>>q[i]; path.clear(); int depth=0; while(dfs(0,depth,-1)==0) depth++; if(!depth) printf("No moves needed"); else for(int i=0;i<path.size();i++) { char c=path[i]+'A'; cout<<c; } printf("\n%d\n",q[7]); } }

标签:

dfs剪枝由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“dfs剪枝