深搜剪枝题目总结

深搜剪枝总结

深搜剪枝分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\n",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\n",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\n",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;
}
posted @ 2019-08-11 18:02  Akaina  阅读(332)  评论(0编辑  收藏  举报