dfs

搜索

刷题单

八数码问题

dfs

重在理解回溯

还有注意死循环

水题——啊会搜索了感人

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m,a[20],c[20];
int ans;
bool cmp(int a,int b){
    return a>b;
}
void dfs(int now,int cnt){//第now只猫,用了cnt个缆车
    if(now==n+1) {ans=min(cnt,ans);return;}
    if(cnt>ans) return;
    for(int i=1;i<=cnt;i++){
        if(c[i]+a[now]>m) continue;
        c[i]+=a[now];now++;
        dfs(now,cnt);
        now--;c[i]-=a[now];
    }
    cnt++;c[cnt]+=a[now];now++;
    dfs(now,cnt);
    now--;c[cnt]-=a[now];cnt--;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+1+n,cmp);
    ans=n;
    dfs(1,1);
    printf("%d
",ans);
    return 0;
}

剪枝

dfs 优秀在于它可以剪枝降低复杂度

•可行性剪枝:如果判断当前节点下面的叶子节点的状态一定不合法,剪枝

•最优性剪枝:如果当前节点下面叶子的答案一定不会比当前最优的答案优,剪枝。

经典例题:

例1:斗地主

啊啊啊啊。。。T飞了。。。发现for(1~n)写成了while(n--)...

阿西吧好不容易过了还有加强版毒瘤数据卡我

放一个ac了普通版的吧。。。天知道4小时血与泪

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int T,n,mi,tot;
int a,b,s[20];
int san(){
    int res=0;
    for(int i=2;i<=16;i++){
        if(s[i]==4) res++;
        if(s[i]==3) res++;
        if(s[i]==2) res++;
        if(s[i]==1) res++;
    }
    return res;
}
//四带二,双顺子,单顺子,三代二,三代一,四张,三张,两张(王),一张
inline void dfs(int num,int ans){
    if(ans>mi) return ;
    if(num==n){ mi=ans;return; }
    tot=san();
    if(tot+ans<mi) mi=tot+ans;//贪心——散打
    int cnt=0;
    for(int i=3;i<=13;cnt=0,i++){//三顺子
        for(int j=i;j<=14;j++)
            if(s[j]>=3) cnt++;
            else break;
        if(cnt>=2){
            for(int j=i;j<=i+cnt-1;j++) s[j]-=3;
            dfs(num+cnt*3,ans+1);
            for(int j=i;j<=i+cnt-1;j++) s[j]+=3;
        }
    }
    cnt=0;
    for(int i=3;i<=12;cnt=0,i++){//双顺子
        for(int j=i;j<=14;j++)
            if(s[j]>=2){
                cnt++;
                if(cnt>=3){
                    for(int j=i;j<=i+cnt-1;j++) s[j]-=2;
                    dfs(num+cnt*2,ans+1);
                    for(int j=i;j<=i+cnt-1;j++) s[j]+=2;
                }
            }
            else break;
    }
    cnt=0;//单顺子
    for(int i=3;i<=10;cnt=0,i++){
        for(int j=i;j<=14;j++)
            if(s[j]>=1){
                cnt++; 
                if(cnt>=5){
                    for(int j=i;j<=i+cnt-1;j++) s[j]--;
                    dfs(num+cnt,ans+1);
                    for(int j=i;j<=i+cnt-1;j++) s[j]++;
                }
            }
            else break;
    }
    for(int i=2;i<=14;i++){
        if(s[i]==4){
            for(int j=2;j<=14;j++)// 4 d 2
                if(s[j]>=2&&j!=i)
                    for(int k=j+1;k<=14;k++)
                        if(s[k]>=2&&i!=k){
                            s[i]-=4;s[j]-=2;s[k]-=2;
                            dfs(num+8,ans+1); 
                            s[i]+=4;s[j]+=2;s[k]+=2;
                        }
            for(int j=2;j<=15;j++)// 4 d 1
                if(s[j]>=1&&j!=i)
                    for(int k=j+1;k<=16;k++)
                        if(s[k]>=1&&i!=k){
                            s[i]-=4;s[j]-=2;s[k]-=2;
                            dfs(num+6,ans+1);
                            s[i]+=4;s[j]+=2;s[k]+=2;
                        }
        }
        if(s[i]>=3){
            for(int j=2;j<=14;j++)//3 d 2
                if(s[j]>=2&&j!=i){
                    s[i]-=3;s[j]-=2;dfs(num+5,ans+1);s[i]+=3;s[j]+=2;
                }
            for(int j=2;j<=16;j++)//3 d 1
                if(s[j]>=1&&j!=i){
                    s[i]-=3; s[j]-=1;dfs(num+4,ans+1);s[i]+=3;s[j]+=1;
                }
        }
    }
    cnt=0;
    if(s[15]==1&&s[16]==1){//炸弹
        s[15]--;s[16]--;
        dfs(num+2,ans+1);
        s[15]++;s[16]++;
    }
}
int main(){
    // freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    T=read();n=read();
    while(T--){
        memset(s,0,sizeof(s));
        mi=n;
        for(int i=1;i<=n;i++){//注意不能写while(n--)。。。n不可变
            a=read();b=read();
            if(a==1) s[14]++;//A > K
            else if(a==0&&b==1) s[15]++;
            else if(a==0&&b==2) s[16]++;
            else s[a]++;
        }
        dfs(0,0);
        printf("%d
",mi);
    }
    return 0;
}

例2:生日蛋糕

dfs+剪枝

1.if(now_v+lastv[c-num]>v) return;现在的体积+剩下最小的体积($第i层是r=i ,h=i $) 若大于 V ,剪掉

2.if(now_s+lasts[c-num]>ans) return;同理

3.if($ now_v+rrh*(c-num)<v $) return;

现在的体积加上剩下最大的体积(每一层都和这层一样)若小于V,剪掉

预处理出剩下最小的体积、面积

for(int i=1;i<=c;i++){
    lasts[i]=lasts[i-1]+2*i*i;
    lastv[i]=lastv[i-1]+i*i*i;
}
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int v,c,ans=0x3f3f3f3f;
int lasts[16];
int lastv[16];
bool f;
void dfs(int r,int h,int now_v,int now_s,int num){
    if(now_v+lastv[c-num]>v) return;
    if(now_s+lasts[c-num]>ans) return;
    if(now_v+r*r*h*(c-num)<v) return;
    if(num==c) {if(now_v==v)ans=min(ans,now_s);f=1;return;}
    for(int i=r-1;i>=c-num;i--)
        for(int j=h-1;j>=c-num;j--)
            dfs(i,j,i*i*j+now_v,2*i*j+now_s,num+1);
}
int main(){
    scanf("%d%d",&v,&c);
    for(int i=1;i<=c;i++){
        lasts[i]=lasts[i-1]+2*i*i;
        lastv[i]=lastv[i-1]+i*i*i;
    }
    for(int r=c;r<=30;r++)
        for(int h=c;h<=30;h++)
            dfs(r,h,r*r*h,r*r+2*r*h,1);
    if(f) printf("%d
",ans);
    else  puts("0");
    return 0;
}

例3:小木棍

剪枝好题

1.对木棍长 从大到小的排序,先加入长的棍,再加入短的棍,相较于先加入短的棍后加入长的棍,搜索数大大减小

2.枚举长度只用枚举 sum 的因数

3.记录上一次枚举的小木棍的长度(f),如果上一次不成功(回溯了)那么下一次这个肯定也不行

每次到了一根新大木棒,f=1

4.if(length==0) return false; 此句的意思是因为所有木棒长度相同皆等价,倘若第一根木棒就匹配失败(就是扫完了所有小木棍都不能连成这个木棒),其它木棒也会失败

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,a[111],sum;
int len,x;//木棒长度
bool cmp(int a,int b){return a>b;}
bool vis[111];
bool dfs(int num,int length,int last){
    if(num>=x) return 1; 
    if(length==len) return dfs(num+1,0,1);
    int f=0;//3
    for(int j=last;j<=n;j++){
        if(!vis[j]&&len>=a[j]+length&&f!=a[j]){
            vis[j]=1;
            if(dfs(num,length+a[j],j+1)) return 1;
            vis[j]=0;
            f=a[j];
            if(length==0 || length+a[j]==len) return 0;
        }
    }
    return 0;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++){
        a[i]=read(); 
        if(a[i]>50) {--i,--n;continue;}
        sum+=a[i];
    }
    sort(a+1,a+1+n,cmp);//1
    for(len=a[1];len<=sum;len++){//2
        if(sum%len) continue;
        memset(vis,0,sizeof(vis));
        x=sum/len;
        if(dfs(1,0,1)){
            printf("%d
",len);break;
        }
    }
    return 0;
}

例4:切蛋糕

(n)块蛋糕,(m)个人,可以切蛋糕,但他不能把两块蛋糕拼起来,但是他又不会给任何人两块蛋糕,问最多可以满足多少人。

Solution

二分答案+贪心

蛋糕从大到小排序,先大蛋糕塞,然后再用小蛋糕,切的话也尽量切小蛋糕

先喂吃的多的人,因为我们是检验答案,这样能减少搜索状态,蛋糕消耗得快。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=10005;
const int M=400005;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
int n,m;
int cake[N],tmp[N],p[M],sc,s[M];
int waste;
bool dfs(int x,int num,int lim) {//人的编号,蛋糕编号,
	if(!x) return 1;
	if(sc-waste<s[lim]) return 0;
	for(int i=num;i<=n;i++) {
		if(tmp[i]<p[x]) continue;
		tmp[i]-=p[x];
		if(tmp[i]<p[1]) waste+=tmp[i];//
		if(p[x]==p[x-1]) {
			if(dfs(x-1,i,lim)) return 1;
		}
		else if(dfs(x-1,1,lim)) return 1;
		//回溯
		if(tmp[i]<p[1]) waste-=tmp[i];
		tmp[i]+=p[x];
	}
	return 0;
}
int main() {
	n=read();
	for(int i=1;i<=n;i++) cake[i]=read(),sc+=cake[i];
	sort(cake+1,cake+1+n);
	m=read();
	for(int i=1;i<=m;i++) p[i]=read();
	sort(p+1,p+1+m);
	while(sc<p[m]) m--;
	for(int i=1;i<=m;i++) s[i]=s[i-1]+p[i];
	int l=0,r=m,ans;
	while(l<=r) {
		int mid=(l+r)>>1;
		waste=0;
		memcpy(tmp,cake,sizeof(tmp));
		if(dfs(mid,1,mid)) ans=mid,l=mid+1;
		else r=mid-1;
	}
	printf("%d
",ans);
	return 0;
}

迭代加深搜索

详见蓝书P108

简而言之,如果知道答案在层数较浅的地方,我们可以限制搜索层数,进行迭代加深搜索。

缺点是会重复搜搜过状态,优点在于当搜索树随层数规模增长飞快时,该方案可飞快

采用 bool 类型返回值时,是一种存在性搜索,并不一定能够得到最优解

例:Addition Chains

http://poj.org/problem?id=2248 poj数据规模较小

优化1:显然倒序枚举更有可能得到可行解

if(a[i]+a[j]>n) continue;
if(a[i]+a[j]<=a[now-1]) break;

两条可行性剪枝

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n,dep,a[10005],ff=1;
bool dfs(int now){
    if(now==dep+1) return a[dep]==n;
    for(int i=now-1;i>=1;i--)
        for(int j=i;j>=1;j--){
            if(a[i]+a[j]>n) continue;
            if(a[i]+a[j]<=a[now-1]) break;
            a[now]=a[i]+a[j];
            if(dfs(now+1)) return 1;
            a[now]=0;
        }
    return 0;
}
int main(){
    while(scanf("%d",&n)&&n){
        memset(a,0,sizeof(a));
        a[1]=1;
        for(dep=1;;dep++)
            if(dfs(2)) break;
        for(int i=1;i<dep;i++)
            printf("%d ",a[i]);
        printf("%d
",a[dep]);
    }
    return 0;
}

然鹅UVA这道题数据范围并不友好

第三个剪枝

对于该数列,有性质 $ f[n]<=2*f[n-1](,所以 如果a[now] *) 2^{(dep-now-1) }<n$ 的话,后面显然不能得到解,break掉

#include<iostream>
#include<cstdio>

using namespace std;

int n,dep,a[10007];
bool dfs(int now){
	if(now==dep+1)
		return a[dep]==n;
	for(int i=now-1;i>=1;i--){
		for(int j=i;j>=1;j--){
			a[now]=a[i]+a[j];
			int b=a[now];
			for(int k=now+1;k<=dep;k++) b*=2;
			if(b<n) break;
			if(dfs(now+1)) return 1;
			a[now]=0;
		}
	}
	return 0;
}
int main(){
	while((~scanf("%d",&n))&&n){
		a[1]=1;
		if(n==1){
			printf("1
");
			continue;
		}
		for(dep=1;;dep++){
			if(dfs(2)){
				for(register int i=1;i<dep;i++)
					printf("%d ",a[i]);
				printf("%d
",a[dep]);
				break;
			}
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/ke-xin/p/13994120.html