【poj1011】 Sticks

5 篇文章 0 订阅

http://poj.org/problem?id=1011 (题目链接)

题意:给出一大堆小棍子的长度,需要把他们拼成几根长度相等的大棍子,求大棍子的最短长度。

solution
  经典搜索题,剪枝剪到手软。
  要得到最小的原始木棍长度,可以按照分段数的长度,依次枚举所有的可能长度L。每次枚举L时,dfs判断是否能用小木棍拼合出整数个L。如果不剪枝,就等着狂TLE吧。
  最优性:
    1.所有木棍的长度和一定能一定能整除大木棍长度L。
    2.大木棍长度一定>=小木棍最长长度。
  可行性:
    3.一只长木棍肯定比几枝短木棍拼成同样长度的用处小,即短小的可以更灵活组合,所以可以对输入的所有木棍按长度排序。
    4.当用木棍i拼合大木棍时,可以从第i+1后的木棍开始搜。因为根据剪枝3,i前面的木棍已经用过了。
    5.用当前最长长度的木棍开始搜,如果拼不出当前设定的L,直接return。
    6.相同长度的木棍不要搜多次。
    7.判断搜到的几根木棍组成长度是否大于L,若大于,return。
    8.判断当前剩下的木棍根数是否够拼的木棍数。

  这里用了一个小小的技巧,函数互相调用,详情见代码。

代码:

// poj1011
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define inf 2147483640
#define Pi 3.1415926535898
#define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;

int n,m,ans,length,a[100010],times,sum;
bool b[100010],flag;

void fit(int x);
void dfs(int x,int len,int lev) {
    if (len==length) {fit(x+1);return;}
    for (int i=lev+1;i<=n;i++)
        if (!b[i] && len+a[i]<=length) {//剪枝5,7
            if (n-times+1<ans-x) return;//剪枝8
            b[i]=1;
            times++;
            dfs(x,len+a[i],i);
            times--;
            b[i]=0;
            int j=i;
            if (flag) return;
            while (i<n && a[i]==a[j]) i++;//剪枝6
            if (i==n) return;
            if (i!=j) i--;
        }
}
void fit(int x) {
    int t;
    if (x>=ans) {flag=1;return;}
    for (int i=1;i<=n;i++) if (!b[i]) {t=i;break;}//剪枝4
    b[t]=1;
    times++;
    dfs(x,a[t],t);
    times--;
    b[t]=0;
}
bool cmp(int a,int b) {return a>b;}
int main() {
    while (scanf("%d",&n)==1) {
        if (n==0) break;
        sum=0;int maxl=0;
        for (int i=1;i<=n;i++) {
            scanf("%d",&a[i]);
            maxl=max(maxl,a[i]);
            sum+=a[i];
        }
        sort(a+1,a+1+n,cmp);//排序,剪枝3
        memset(b,0,sizeof(b));
        for (int i=n;i>=1;i--) {
            ans=i;length=sum/i;
            if (i==1) break;
            if (sum%i>0 || length<maxl) continue;//剪枝1,2
            memset(b,0,sizeof(b));
            times=0;
            flag=0;
            fit(1);
            if (flag) break;
        }
        printf("%d\n",sum/ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值