关于dfs剪枝技巧研究简记

例一、数的划分

个人觉得最剪枝的方法是直接枚举答案 

#include<bits/stdc++.h>
#define re return
#define inc(i,l,r) for(int i=l;i<=r;++i)
using namespace std;
template<typename T>inline void rd(T&x)
{
    char c;bool f=0;
    while((c=getchar())<'0'||c>'9')if(c=='-')f=1;
    x=c^48;
    while((c=getchar())>='0'&&c<='9')x=x*10+(c^48);
    if(f)x=-x;
}

int n,m,ans;
inline void dfs(int sum,int k,int last)
{
    if(k==1)//可行性剪枝 
    {
        ++ans;
        re ;
    }
    
    inc(i,last,(n-sum)/k)//上下界剪枝 
        dfs(sum+i,k-1,i);
}
int main()
{
    rd(n);rd(m);
    dfs(0,m,1);
    printf("%d",ans);
    re 0;
} 
View Code

例二、[NOI1999]生日蛋糕

疯狂剪枝,你就赢了

#include<bits/stdc++.h>
#define re return
#define inc(i,l,r) for(int i=l;i<=r;++i)
#define dec(i,l,r) for(int i=r;i>=l;--i)
using namespace std;
template<typename T>inline void rd(T&x)
{
    char c;bool f=0;
    while((c=getchar())<'0'||c>'9')if(c=='-')f=1;
    x=c^48;
    while((c=getchar())>='0'&&c<='9')x=x*10+(c^48);
    if(f)x=-x;
}

int n,m,ans=2147483647,minv[25],mins[25];
inline void pre()
{
    inc(i,1,m)
    {
        minv[i]+=minv[i-1]+i*i*i;
        mins[i]+=mins[i-1]+2*i*i;
    }
    
}

inline void dfs(int nowv,int nows,int dep,int lastr,int lasth)
{
    if(!dep)
    {
        if(nowv==n)
        ans=min(ans,nows);
        re ;
    } 
    if(nows+mins[dep]>=ans)re;//最优性剪枝一:当前表面积+上面层取最小表面积大于当前答案 
    if(nowv+minv[dep]>n)re ;//可行性剪枝:是否满足体积为N 
    if(2*(n-nowv)/lastr+nows>=ans)re ;//不等式放缩得到的最优性剪枝二 
    int nowmaxv=n-minv[dep-1]-nowv; //同下 
    dec(i,dep,min((int)sqrt(nowmaxv/dep),lastr-1))//枚举r
    //上下界剪枝
    //+搜索顺序优化 
    {
        dec(j,dep,min(nowmaxv/i/i,lasth-1))//枚举h 
        dfs(i*i*j+nowv,2*i*j+nows,dep-1,i,j);
    } 
} 
int main()
{
    freopen("in.txt","r",stdin);
    rd(n),rd(m);
    pre();
    
    int nowmaxv=n-minv[m-1]; //当前层最大体积 
    dec(i,m,(int)sqrt(nowmaxv/m))//枚举r
    {
        dec(j,m,nowmaxv/i/i)
        dfs(i*i*j,i*i+2*i*j,m-1,i,j);//最下层,表面积要加上底面积 
    } 
    printf("%d",ans);
    re 0;
} 
View Code

例三、小木棍 

题目描述

乔碧萝殿下有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过5050。

现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。

给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

#include<bits/stdc++.h>
#define re return
#define ll long long
#define inc(i,l,r) for(int i=l;i<=r;++i) 
#define dec(i,l,r) for(int i=l;i>=r;--i)

using namespace std;
int n,maxx,a[66],use[66],k,x,sum,m,len,next[66]; 
inline int read()
{
    int date=0;char c=' ';
    while('0'>c||c>'9')c=getchar();
    while('0'<=c&&c<='9'){date=(date<<3)+(date<<1)+(c^48);c=getchar();}
    re date;
}

void vv(int num,int last,int rest)
{
    if(rest==0)//如果拼接好当前木棍 
    {
        if(num==m-1)printf("%d",len),exit(0);//找到答案 
        inc(i,2,n)
        if(!use[i])
        {    use[i]=1;
            vv(num+1,i,sum/m-a[i]);//进行下一轮 
            use[i]=0;break;    }            
    }
    
    int l=last+1, r=len, mid;
    while(l<r)
    {
        mid=(l+r)>>1;
        if(a[mid]<=rest) r=mid;
        else l=mid+1;
    }
    inc(i,l,k)//上下界剪枝&&顺序 
    if(!use[i]&&a[i]<=rest)
    {    
        use[i]=1;
        vv(num,i,rest-a[i]);
        use[i]=0;
        if(rest==a[i] || rest==len) return; //当前木棍不可行 ,无论如何都不可行 
        i=next[a[i]];//跳过相同长度 
    }
    re;
}

bool cmp(int a,int b)
{
    re a>b;
}

int main()
{
//    freopen("testdata.in","r",stdin);
    n=read();
    inc(i,1,n)
    {
        x=read();
        if(x<=50){a[++k]=x;sum+=x;
            maxx=max(maxx,x);}
    }
    
    sort(a+1,a+k+1,cmp);//从大到小枚举 
    //优化搜索顺序 
    
    inc(i,1,k)
    {
        int j=i+1;
        if(a[i]==a[j])++j;
        next[a[i]]=j-1;    //优化搜索顺序 
    }
    
    int i;use[1]=1;
    for(i=maxx;i<sum;++i)//上下界剪枝 
    {
    
        if(sum%i)continue;
        m=sum/i;
        len=i;
        memset(use,0,sizeof(use));
        vv(0,1,len-a[1]);
    }
    printf("%d",i);
    re 0;
}

 
View Code

例 4 Addition Chains

好好的一到迭代加深

#include<bits/stdc++.h>
#define re return
#define ll long long
#define inc(i,l,r) for(int i=l;i<=r;++i) 
#define dec(i,l,r) for(int i=l;i>=r;--i)

using namespace std;
long long n,m,a[1005],deep;
template<typename T>inline void rd(T&x)
{
    char c;bool f=0;
    while((c=getchar())<'0'||c>'9')if(c=='-')f=1;
    x=c^48;
    while((c=getchar())>='0'&&c<='9')x=x*10+(c^48);
    if(f)x=-x;
}

inline bool dfs(int cnt)
{
    if(cnt==deep)
        re a[cnt]==n;
    if(1ll*a[cnt]*(1<<(deep-cnt))<n)re 0;
    //可行性剪枝,当前最大数2倍跳到最后能否大于n? 
    inc(i,1,cnt)
    inc(j,i,cnt)
    if(a[i]+a[j]>n)break;
    else if(a[i]+a[j]>a[cnt])
    {
        a[cnt+1]=a[i]+a[j];
        if(dfs(cnt+1)) re 1;
    }
    re 0;
}

int main()
{
//    freopen("testdata.in","r",stdin);
    a[1]=1;
    while(2333)
    { 
        scanf("%d",&n);
        if(!n)re 0;
        printf("1");
        for(deep=1;;++deep)
        if(dfs(1))
        {
            inc(j,2,deep)
            printf(" %d",a[j]);
            break;
        }    
        printf("\n");
    } 
    re 0;
}

 
View Code

非要弄成普通深搜剪枝

#include<bits/stdc++.h>
#define re return
#define ll long long
#define inc(i,l,r) for(int i=l;i<=r;++i) 
#define dec(i,l,r) for(int i=l;i>=r;--i)

using namespace std;
int n,m,a[105],b[105],ans;
template<typename T>inline void rd(T&x)
{
    char c;bool f=0;
    while((c=getchar())<'0'||c>'9')if(c=='-')f=1;
    x=c^48;
    while((c=getchar())>='0'&&c<='9')x=x*10+(c^48);
    if(f)x=-x;
}

inline void dfs(int cnt)
{
    if(cnt>=ans) re;
    if(a[cnt]==n)
    {
        inc(i,1,cnt)
        b[i]=a[i];
        ans=cnt;
        re ;
    }
    for(int i=cnt;(a[i]<<1)>a[cnt];--i)
    {
        dec(j,i,1)
        if(a[j]+a[i]<=a[cnt])break;
        else if(a[i]+a[j]<=n)
        {
            a[cnt+1]=a[i]+a[j];
            dfs(cnt+1);
        }
    }
}

int main()
{
//    freopen("testdata.in","r",stdin);
    a[1]=1;a[2]=2;
    while(scanf("%d",&n))
    { 
    if(!n)re 0;
        if(n==1)
        printf("1");
        else if(n==2)
        printf("1 2");
        else 
        {
            ans=2147483647;
            dfs(2);
            inc(i,1,ans)
            printf("%d ",b[i]);
        }
        
        printf("\n");
    } 
    re 0;
}

 
View Code

猜你喜欢

转载自www.cnblogs.com/lsyyy/p/11443885.html