【刷题】【搜索】

1>生日蛋糕

做一个蛋糕,规定体积为n(pai),层数为n,

m,n,ri,hi均为整数

且每一层ri大于上一次rj,每一层高度hi大于hj

求最大的s表

 

先列方程:

V=n=∑ri*ri*hi 

S=rm*rm +∑2*ri*hi

(1<=ri<=m)

 

可以看出,这里有明显的枚举,写dfs的特征,

枚举层数,记录上次的r,h,和直到上一层的s,v,

直到最后一层,与V比较,等则更新ans

 

但是n<=20000,

最大的r是132,最大的h是20000,

过大,枚举过程肯定要剪枝,

 

剪枝分为

1)可行性剪枝(减v)

2)最优性剪枝(减s)

 

a.加上估值函数mn_v,mn_s,

分别表示前i层,半径1-i枚举,高度1-i枚举的值,

这样就为搜索过程提供了一个:可行下界

以后的s和v不会过大,

(在dfs前递推预处理)

 

b.感觉这个剪枝不太给力,

那么我们就再加入一点,减去更多的枝

看看这个v,

似乎还可以设置一个可行上界?!

就是每次的ri*ri*hi*res +v <V

不过这个因为和ri和hi的具体值有关(ri,hi不确定),所以放在枚举中较好

 

那么再看看mn_s,似乎存在着一个问题:

mn_s和mn_v不太贴合,这个mn_s太理想

如果我们去贴合mn_v这个量,就要加大一层的r或h,那s与mn_s就差的多了,

最后就算与ans比较,成功更新的概率不高,

所以我们想啊想,想着再换一种思路求s的界限

 

看看式子:s是从侧面来的,

将s与V建立联系,就可以让s与ans更接近,不会出现中间突然的偏差,

发现:

vi=ri*ri*hi = ri*hi*2 (/2)(*ri) =si

则:resv=(n-v)

S<= s+resv/rm*2 

 

所以完成:

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m;
int inf=99999999;
int mn_s[20],mn_v[20];
int ans;

int rd;
void dfs(int pos,int s,int v,int mr,int mh) 
{
    if(pos==0)
    {
        if(v==n) ans=min(ans,s);
        return ;
    }
    
    if(!mh || !mr) return ;
    if(s+ mn_s[pos] >ans) return ; 
    if(v+ mn_v[pos] >n) return ;
    if(s+ (n-v)/mr *2 >ans) return ;
    
    for(int i=mr;i;i--)
    {    
        if(pos==m) s=i*i;
        
        int mx_h=min(mh, (n-v-mn_v[pos-1])/(i*i));
        //printf("%d %d %d %d\n",pos,s,v,mx_h);
        for(int j=mx_h;j>=pos;j--)
            dfs(pos-1,s+2*i*j,v+i*i*j,i-1,j-1);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    
    for(int i=1;i<=m;i++)//枚举的是层数,表示每层的最小s,v 
        mn_v[i]=mn_v[i-1] +i*i*i,
        mn_s[i]=mn_s[i-1] +i*i*2;//这里的mn_s不包括底面 
    
    ans=inf;
    dfs(m,0,0,sqrt(n),n);
    
    if(ans==inf) printf("0\n");
    else printf("%d\n",ans);
    
    return 0;
}

2>addition chain

一道不容易想,怎么剪枝的题

然后居然是迭代加深...

震惊,然后因为层数不多,我用的是优先队列去重,减少无用递归

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
int n,maxd;
int d[10003],t[10003];

bool dfs(int pos)
{
    if(pos>maxd) return false;
    
    int nw;
    priority_queue <int> q;
    for(int i=pos-1;i;i--)
        for(int j=i;j;j--)
        {
            nw=d[i]+d[j] ;
            if(pos<maxd ) 
            {
                if((nw<<(maxd-pos))<n) break;
                //这里其实还可以多加一个可行性剪枝:计算之后可以到达的最大结果 
                if(nw<n && nw>d[pos-1]) q.push(nw);//注意这里还要求单调递增 
            }
            else if(nw==n)
            {
                for(int i=1;i<maxd;i++) 
                    printf("%d ",d[i]);
                printf("%d\n",n);
                return true;
            }
        }
    
    bool ok=false;
    while(!q.empty() && !ok)
    {
        nw=q.top(); q.pop();
        while(!q.empty() && q.top()==nw) q.pop();
        d[pos]=nw;
        ok=dfs(pos+1);
    }
    return ok;
}

int main()
{
    d[1]=1,d[2]=2;
    while(~scanf("%d",&n) && n )
        if(n<4) 
        {
            for(int i=1;i<=n;i++) printf("%d ",i);
            printf("\n");
        }
        else
        { 
            for(maxd=3; ;maxd++)if(dfs(3)) break;
        }
    
    return 0;
}

3>打开灯泡

 

转载于:https://www.cnblogs.com/xwww666666/p/11433609.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值