• 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;
    }
    
  • 相关阅读:
    tomcat 乱码问题
    mongo获取lbs数据
    sping mvc+uploadify 上传文件大小控制3部曲
    jstack 查看线程状态
    nginx 限流配置
    查找占用CPU高线程
    redis sentinel无法启动问题
    GC垃圾回收机制
    ASP.NET 生命周期
    Javascript验证手机号码正则表达式
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13994120.html
Copyright © 2020-2023  润新知