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;
    }
    
  • 相关阅读:
    HDU 2899 Strange fuction
    HDU 2899 Strange fuction
    HDU 2199 Can you solve this equation?
    HDU 2199 Can you solve this equation?
    Java实现 LeetCode 700 二叉搜索树中的搜索(遍历树)
    Java实现 LeetCode 700 二叉搜索树中的搜索(遍历树)
    Java实现 LeetCode 700 二叉搜索树中的搜索(遍历树)
    Java实现 LeetCode 699 掉落的方块(线段树?)
    Java实现 LeetCode 699 掉落的方块(线段树?)
    Java实现 LeetCode 699 掉落的方块(线段树?)
  • 原文地址:https://www.cnblogs.com/Akaina/p/11335860.html
Copyright © 2011-2022 走看看