zoukankan      html  css  js  c++  java
  • 深搜剪枝题目总结

    深搜剪枝总结

    深搜剪枝分5种:

    1.优化搜索顺序

    2.排除等效冗余

    3.可行性剪枝

    4.最优性剪枝

    5.记忆化搜索

    数的划分

    可行性(上下界)剪枝

    由于答案中的数不考虑顺序,不妨设它是单调递增的

    则对a[i],下界是a[i-1],上界是rest/(k-i+1)

    #include <iostream>
    #include <cstdio>
    using namespace std;
    int n,k;
    int ans=0;
    int a[10];
    void dfs(int layer,int rest)
    {
        if(rest==0)return ;
        if(layer==k-1&&rest<a[layer])return ;
        if(layer==k-1&&rest>=a[layer])
        {
            ans++;return;
        }
        int x=rest/(k-layer);
        for(int i=a[layer];i<=x;i++)
        {
            a[layer+1]=i;
            dfs(layer+1,rest-i);
        }
    }
    int main()
    {
        scanf("%d%d",&n,&k);
        a[0]=1;
        dfs(0,n);
        printf("%d",ans);
        return 0;
    }
    

    生日蛋糕

    最优性剪枝:当前面积+之后最小面积>ans

    可行性剪枝:当前体积-之后最大体积>0 (可以预处理)

    上下界剪枝:上界:上一层的半径和体积(或通过剩余体积计算,二者取最小值)

    ​ 下界:层数

    搜索顺序剪枝:倒序枚举,能更快达到目标体积,加速最优性剪枝

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    int n,m;
    int ans;
    void dfs(int cen,int restv,int r,int h,int s)
    {
        if(s+m-cen>=ans)return ;
        if(cen==m)
        {
            if(restv==0)ans=min(ans,s);
            return ;
        }
        if(restv<=0)return ;
        if(restv-r*r*h*(m-cen)>0)return ;
        for(int i=r-1;i>=m-cen;i--)
        {
            for(int j=h-1;j>=m-cen;j--)
            {
                if(cen!=0)
                {
                    dfs(cen+1,restv-i*i*j,i,j,s+2*i*j);
                }
                else
                {
                    dfs(cen+1,restv-i*i*j,i,j,s+2*i*j+i*i);
                }
            }
        }
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        ans=1e9;
        dfs(0,n,sqrt(n),sqrt(n),0);
        printf("%d",ans);
        return 0;
    }
    

    小木棍

    最优性剪枝:1.木棍原来的长度一定大于等于所有木棍中最长的那根,小于等于木棍的长度和,枚举时只需枚举到

    木棍长度和的一半,因为如果超过一半,那只能是由一根原木棍切成所有的木棍

    ​ 2.设所有木棍长度和为sum则原长度一定能被sum整除

    可行性剪枝:3.短的木棍一定比长度更灵活,所以先从长到短排序,把难搞的先搞了,其实很多dp题也有着类似

    的贪心思想(比如洛谷P4138挂饰),需要先排序再进行操作,不然就可能会WA。

    ​ 4.从第一根长度小于或等于当前木棍剩余长度的开始搜索(二分查找)

    ​ 5.预处理出一个数组表示跟当前木棍长度相等的木棍的最后一根,因为如果当前木棍搜不出结果,等

    长的木棍也同样搜不出结果(如果能搜出结果,那pd就会为1,这于深搜的性质有关)

    ​ 6.用pd记录是否已查找到符合条件的情况,若找到,立刻返回

    小细节:注意搜索树的性质,一定要回溯,读入数据时要过滤掉长度大于50的木棍

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    using namespace std;
    int n,maxx,sum;
    int a[80],ans,pd=0,len;
    int pre[80],used[80];
    bool cmp(int x,int y)
    {
        return x>y;
    }
    void dfs(int k,int rest,int last)
    {
        if(pd)return ;
        if(rest==0)
        {
            if(k==sum/len)
            {
                pd=1;return;
            }
            int cc=0;
            for(int i=1;i<=n;i++)
            {
                if(!used[i])
                {
                    cc=i;
                    used[i]=1;
                    break;
                }
            }
            dfs(k+1,len-a[cc],cc);
            used[cc]=0;//一定要记得回溯,《一本通》上这儿忘了回溯
            if(pd)return;
        }
        int l=last+1,r=n,cnt=n+1;
        while(l<=r)//优化4
        {
            int mid=(l+r)>>1;
            if(a[mid]<=rest)
            {
                cnt=mid;r=mid-1;
            }
            else l=mid+1;
        }
        for(int i=cnt;i<=n;i++)
        {
            if(!used[i])
            {
                used[i]=1;
                dfs(k,rest-a[i],i);
                used[i]=0;
                if(pd)return;//优化6
                if(rest==a[i])return;
                i=pre[i];//优化5
                if(i==n)return;
            }
        }
    }
    void solve()
    {
        for(int i=maxx;i<=sum/2;i++)//优化1
        {
            len=i;
            if(sum%i==0)//优化2
            {
                dfs(0,0,0);
                if(pd==1)
                {
                    ans=i;
                    return ;
                }
            }
        }
        return ;
    }
    int main()
    {
        scanf("%d",&n);
        int tot=0;
        for(int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            if(x>50)continue;
            a[++tot]=x;
            maxx=max(maxx,x);
            sum+=x;
        }
        n=tot;
        sort(a+1,a+n+1,cmp);//优化3
        ans=sum;
        for(int i=1;i<=n;i++)
        {
            int j=i;
            while(a[i]==a[j])j++;
            pre[i]=j-1;
        }
        solve();
        printf("%d
    ",ans);
        return 0;
    }
    

    Addition Chains

    迭代加深+剪枝

    因为这道题要求的是满足条件的最小深度,所以用迭代加深会更可做一些

    这道题还有一个显然的贪心性质,每一个数一定等于上一个数加上 之前的数(可以是上一个数),否则的话如果是两个上一个数之前的数相加,那么上一个数就可以得到了,不需要多占一个地方。

    可行性剪枝:数列每增加一个数,这个数的大小最大是在上一个数的基础上*2,如果剩下的长度中每个数都达到最大值,但最后一个数任然小于n则返回

    优化搜索顺序:倒序枚举,因为这样能够更快地到达n(这道题是一道典型的优化搜索顺序题)

    小细节:这道题特别坑的一个点是针对每组数据,行尾不能有空格 真的玄学坑

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define ll long long
    ll a[10010],dep,n;
    int pd=0;
    void dfs(ll step)
    {
        if((a[step]*(1<<(dep-step)))<n)return;
        if(step==dep)
        {
            if(a[step]==n)pd=1;
            return;
        }
        for(ll i=step;i>=1;i--)
        {
            a[step+1]=a[i]+a[step];
            dfs(step+1);
            if(pd)return;
            a[step+1]=0;
        }
    }
    void solve()
    {
        a[1]=1;
        for(ll i=1;i<=n;i++)
        {
            dep=i;
            dfs(1);
            if(pd)return ;
        }
    }
    int main()
    {
        while(scanf("%lld",&n))
        {
            if(n==0)break;
            memset(a,0,sizeof(a));
            dep=0;pd=0;
            solve();
            for(ll i=1;i<dep;i++)printf("%lld ",a[i]);
            printf("%d
    ",a[dep]);
        }
        return 0;
    }
    

    埃及分数

    迭代加深+剪枝

    上下界剪枝:上界:max(上一个分母+1,b/a+1(通过乘除法运算可以推得))

    ​ 下界:当前深度所得到的答案的最后一位

    可行性剪枝:当前分数 - 剩下分数的最大和>0,则舍去

    至于题目要求的不能选的数,可以用set来储存(插入和查询都是logn的复杂度)

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<set>
    using namespace std;
    #define ll long long
    ll A,B,T,k,len;
    ll ans[1005],cur[1005];
    int pd=0;
    set<ll> s;
    ll read()
    {
        ll x=0;
        char ch;ch=getchar();
        while(ch<'0'||ch>'9')ch=getchar();
        while(ch>='0'&&ch<='9')
        {
            x=x*10+ch-'0';
            ch=getchar();
        }
        return  x;
    }
    ll gcd(ll x,ll y){return y==0?x:gcd(y,x%y);}
    bool better()
    {
        for(int i=len;i>=1;i--)
        {
            if(ans[i]==cur[i])continue;
            return ans[i]==-1||cur[i]<ans[i];
        }
        return false;
    }
    ll minn;
    void dfs(ll a,ll b,ll last,ll cnt)
    {
        if(cnt==len)
        {
            if(b%a!=0)return;
            b/=a;
            if(s.count(b))return;
            cur[cnt]=b;
            if(better())
            {
                pd=1;
                minn=b;
                for(int i=1;i<=len;i++)ans[i]=cur[i];
            }
            return ;
        }
        last++;
        last=max(last,b/a+1);
        for(int i=last;i<=minn;i++)
        {
            if(a*i-b*(len-cnt+1)>=0)return;
            if(s.count(i))continue;
            ll g=gcd(a*i-b,b*i);
            cur[cnt]=i;
            dfs((a*i-b)/g,b*i/g,i,cnt+1);
        }
    }
    int main()
    {
        T=read();int tot=0;
        while(T--)
        {
            tot++;
            A=read();B=read();k=read();pd=0;minn=1e7;
            s.clear();
            printf("Case %d: %lld/%lld=",tot,A,B);
            for(int i=1;i<=k;i++)
            {
                int x=read();s.insert(x);
            }
            for(int i=1;i<=1e3;i++)
            {
                len=i;
                memset(ans,-1,sizeof(ans));
                dfs(A,B,0,1);
                if(pd==1)break;
            }
            for(int i=1;i<len;i++)printf("1/%lld+",ans[i]);
            printf("1/%lld
    ",ans[len]);
        }
        return 0;
    }
    

    平板涂色

    深搜+剪枝

    这道题最多只能刷16次,感觉不需要迭代加深

    最优性剪枝:1.当前涂色次数>ans,就返回

    可行性剪枝:2.在搜索前需要先按照y坐标为第一关键字,x坐标为第二关键字从小到大进行排序

    这道题用一个二维的f数组,第一维是i,第二维是j,来表示j是否是紧靠i上方的矩形,若f的值为1,则为是,f的值为0,则为否。我将点的坐标直接转化为单位小正方形的坐标进行处理。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    using namespace std;
    int n;
    int f[20][20],ans=0;//f[i][j]表示第i个矩形上方是否是第j个矩形
    struct node{
        int x1,y1,x2,y2,col;
    }a[25];
    bool cmp(node x,node y)
    {
        if(x.y2!=y.y2)return x.y2<y.y2;
        return x.x1<y.x1;
    }
    int had(int lx1,int rx1,int y1,int lx2,int rx2,int y2)
    {
        if(y1==y2+1)
        {
            if(min(rx1,rx2)>=max(lx1,lx2))return 1;
        }
        return 0;
    }
    int vis[50];
    int check(int x)
    {
        for(int i=1;i<x;i++)
        {
            if(f[x][i]==1)
            {
               if(vis[i]==1)continue;
               return 0;
            }
        }
        return 1;
    }
    void dfs(int dr,int last,int nowco,int cnt)
    {
        if(dr>=ans)return ;
        if(cnt==n)
        {
            ans=min(ans,dr);
            return ;
        }
        for(int i=1;i<=n;i++)
        {
            if(vis[i])continue;
            if(check(i)==1)
            {
                if(a[i].col==nowco)
                {
                    vis[i]=1;
                    dfs(dr,i,a[i].col,cnt+1);
                    vis[i]=0;
                }
                else
                {
                    vis[i]=1;
                    dfs(dr+1,i,a[i].col,cnt+1);
                    vis[i]=0;
                }
            }
        }
    }
    int main()
    {
        scanf("%d",&n);ans=1e9;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d%d%d%d",&a[i].y1,&a[i].x1,&a[i].y2,&a[i].x2,&a[i].col);
            a[i].x1++;a[i].y1++;
        }
        sort(a+1,a+n+1,cmp);
        for(int i=2;i<=n;i++)
        for(int j=1;j<i;j++)
        {
            if(had(a[i].x1,a[i].x2,a[i].y1,a[j].x1,a[j].x2,a[j].y2)==1)f[i][j]=1;
        }
        dfs(0,0,0,0);
        printf("%d",ans);
        return 0;
    }
    

    靶形数独

    深搜+剪枝

    计算方格(x,y)所在小九宫格的公式:(x-1)/3*3+(y-1)/3+1

    方格的分值直接用一个数组储存

    剪枝:玩过数独的人应该知道,我们需要从未知数字少的一行开始填,所以先按照每一行已知数的数目从大到小排序,先处理已知数多的行

    用三维数组vis中的vis[0][line][i]表示第line行的i是否被取过
                   vis[1][column][i]表示第column列的i是否被取过
                   vis[3][palace][i]表示第palace个小九宫格的i是否被取过
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    int score[10][10]=
    {
        {0,0,0,0,0,0,0,0,0,0},
        {0,6,6,6,6,6,6,6,6,6},
        {0,6,7,7,7,7,7,7,7,6},
        {0,6,7,8,8,8,8,8,7,6},
        {0,6,7,8,9,9,9,8,7,6},
        {0,6,7,8,9,10,9,8,7,6},
        {0,6,7,8,9,9,9,8,7,6},
        {0,6,7,8,8,8,8,8,7,6},
        {0,6,7,7,7,7,7,7,7,6},
        {0,6,6,6,6,6,6,6,6,6}
    };
    int mp[15][15];
    int vis[4][15][15];
    struct node{
       int cnt,id;
    }a[15];
    int palace(int x,int y)
    {
        return (x-1)/3*3+(y-1)/3+1;
    }
    bool cmp(node x,node y){return x.cnt>y.cnt;}
    int tot=1;
    int ans=0;
    int sum=-1;
    void dfs(int line,int column)
    {
        if(tot>=10)
        {
            sum=max(sum,ans);
            return ;
        }
        if(column==10)
        {
            tot++;
            dfs(a[tot].id,1);
            tot--;
            return ;
        }
        if(!mp[line][column])
        {
            for(int i=1;i<=9;i++)
            {
                if(vis[0][line][i] || vis[1][column][i] || vis[2][palace(line,column)][i])continue;
                vis[0][line][i]=1;
                vis[1][column][i]=1;
                vis[2][palace(line,column)][i]=1;
                mp[line][column]=i;
                ans+=i*score[line][column];
                dfs(line,column+1);
                vis[0][line][i]=0;
                vis[1][column][i]=0;
                vis[2][palace(line,column)][i]=0;
                mp[line][column]=0;
                ans-=i*score[line][column];
            }
        }
        else dfs(line,column+1);
        return;
    }
    int main()
    {
        for(int i=1;i<=9;i++)
        {
            a[i].id=i;
            for(int j=1;j<=9;j++)
            {
                scanf("%d",&mp[i][j]);
                if(mp[i][j]!=0)
                {
                    vis[0][i][mp[i][j]]=1;
                    vis[1][j][mp[i][j]]=1;
                    vis[2][palace(i,j)][mp[i][j]]=1;
                    a[i].cnt++;ans+=mp[i][j]*score[i][j];
                }
            }
        }
        sort(a+1,a+10,cmp);
        dfs(a[1].id,1);
        printf("%d",sum);
        return 0;
    }
    
  • 相关阅读:
    解决异常:“The last packet sent successfully to the server was 0 milliseconds ago. ”的办法
    关于时间复杂度
    关于如何在MyEclipse下修改项目名包名,以及类
    Error filterStart
    类A是公共的,应在名为A.java的文件中声明错误
    Eclipse快捷键大全
    JVM 是用什么语言写的?
    退出cmd命令
    Java 如何对文件进行多个Object对象流的读写操作
    SublimeText2 快捷键一览表
  • 原文地址:https://www.cnblogs.com/Akaina/p/11335860.html
Copyright © 2011-2022 走看看