【洛谷】P1120 小木棍[数据加强版]

来源:互联网 发布:js聚合物水泥防水阴角 编辑:程序博客网 时间:2024/04/30 12:07

传送门


这道题目因为加强了数据,所以博客以前的题解不能够满足这道题目的时间复杂度,当然还是使用暴力,但是得多一点剪枝。

  • 剪枝1:将木棍从大到小排序,这样搜索就可以少一些可能性。
  • 剪枝2:从最大的一根木棍开始枚举,一直到木棍长度之和/2,因为最糟糕的情况就是每一根木棍只与自己搭配,所以这种情况在最后输出,再就是两两搭配,所以答案就是总和/2,这样可以减去差不多一半的复杂度。
  • 剪枝3:如果前t1个木棍都订好了,最后一根木棍想都不用想都可以搭配。
  • 剪枝4:缩小搜索范围,但是要记得每一次s==m的时候初始化。
  • 剪枝5:如果着一根木棍被用过了,或者是它与当前木棍之和加起来超过了我们想要的木棍总长,就不选它。
  • 剪枝6:如果我当前的木棍和上一个木棍是一样的,而且上面一根木棍不能够凑出答案,那我这一根也不行!
  • 剪枝7:如果我当前的长度,加上后面所有的木棍的长度都凑不出来一个总木棍长,就可以放弃这种方法。
  • 剪枝8:如果当前状态(新的)搜索不出来一根新木棍,就反悔。
  • 剪枝9:如果我现在这根木棍加上总和才可以凑出m,那么后面就凑不出来了!
    具体看代码:
//洛谷P1120 小木棍#include<cstdio>#include<cstdlib>#include<algorithm>int n,cnt,sum,maxx,t,m;int a[100],p[110];bool b[100];using namespace std;int cmp(const int&a,const int&b) {    return a>b;}void dfs(int l,int s,int last) {    if(s==m) {//如果找好了一根木棍        s=0;//累加器清零        l++;//累记的以前的总木棍个数+1        last=1;//下次从第一个木棍开始搜索    }    if(l==t-1) {//这是第3个剪枝        printf("%d\n",m);exit(0);    }    for(int i=last; i<=cnt; i++) {//这是第4个剪枝        if(s+a[i]>m||b[i]) continue;//这是一个可行性剪枝[剪枝5]        if(a[i]==a[i-1]&&!b[i-1]) continue;//这是第6个剪枝        if(s+p[i]<m)return;//这是第7个剪枝[很重要!]        b[i]=1;        dfs(l,s+a[i],i+1);        b[i]=0;//这是普通搜索加回溯        if(s==0)return;//这是第8个剪枝        if(s+a[i]==m)return;//这是第9个剪枝    }}void init() {    int x;    for(int i=1; i<=n; i++) {        scanf("%d",&x);        if(x<=50) {            a[++cnt]=x;//重新挑选            sum+=x;//累加            maxx=max(maxx,x);//计算最大值        }    }}int main() {    int i,j,k;    scanf("%d",&n);    init();//读入,这里记住一定要排除50以上的!    sort(a+1,a+n+1,cmp);//第1个剪枝    for(i=n;i>=1;i--)        p[i]=p[i+1]+a[i];//计算一个后缀和,剪枝需要    for(i=maxx; i<=sum/2; i++) {//第2个剪枝        if(sum%i==0) {//如果它有成为答案的可能才搜索,不要浪费时间            t=sum/i;//这个表示以前总的木棍个数            m=i;//这是以前木棍的长度            dfs(0,0,0);//搜索        }    }    printf("%d\n",sum);//如果循环结束了程序还没有结束,就只能输出这个,见上分析    return 0;}

这道题目很适合练习剪枝的同学们做,确实是有些复杂,但是应该有比我这个代码更快的[二分答案],希望大家能够写出来!