搜索 和优化剪枝

来源:互联网 发布:mysql的大于等于符号 编辑:程序博客网 时间:2024/04/29 18:33

[USACO 4.1]Fence Rails

Qheldiv posted @ 2013年2月19日 11:41 in 题解 with tags 搜索 USACO , 178 阅读

4.1中的奶牛家秘术和Fence Rails都是要加好几个剪枝才可以AC的问题

搜索对象:每个栅栏(rail)对应那个Board(可能的深度太大而答案很可能在很浅的地方,所以迭代深度去找(DFSID),如果某个深度找到一个可行解,立刻退出,再搜索下一个深度)

一般优化都可以从以下3点考虑:

1.可行性剪枝   如果我们预料到某种情况无论再怎么搜索也得不到可行解,那就直接退出

2.最优化剪枝   如果我们预料到某种情况无论再怎么走索也得不到比当前最优解更优的解,那也直接退出

3.搜索顺序      良好的搜索顺序会更快的找到答案

其他技巧因问题而不同

那么Fence Rails可以这样剪枝:

1.可行性:     如果当前剩下的不可用的Board(即不能再切出rails的board)的和比board总面积减去rails的总面积还大,那肯定不可行(通俗地讲就是不能再装Rails的面积比装满所有rails剩下来的面积大)

2.搜索顺序:  容易想到,如果刚开始的时候就用大的rail塞到较小的board中,很快就能找到可行解

3.注意有很多重复,考虑到重复的元素位置不同没有影响,所以如果上一个元素和该元素相同,那么所在的board就直接从上一个开始枚举

这样就可以AC了

我的代码(有些慢):

All tests OK.

Hal Burch 's Analysis:
Note that if there is a way to cut k rails, there is a wa
y to cut the k shortest rails, so we will only consider subsets of the rails that contain the k shortest rails. Also, we know the sum or the rails cut cannot exceed the sum of the lengths of the rails, so we can stop our DFS-ID if it finds a way to cut the largest set of shortest rails such that the sum of the lengths of the rails is less than the sum of the board lengths.

Since finding a board from which to cut a longer rail is more difficult than finding a board for a shorter rail, wewill perform the search in such that the longest rail is cut first, then the second longest rail, etc.

Also, if two rails are of the same length, then cutting the first from board A and the second from board B is the same as cutting the first from board B and the second from board A, so within sets of rails of the same length, we will ensure that the rails are cut from boards of non-decreasing index.

If there are two boards of the same length, we need to check cutting the rail from only the first.

If there is a board of the same length of the rail, then cutting that rail from that board is optimal.

If, when cutting a board, we get a length of board less than the shortest rail, it is useless, and we can discard it from consideration. This reduces the total amount of board-feet left from which to cut the rest of the rails. 

Another solution from Hassan Eslami:

USACO 4.1 Fence Rails 解题报告

这道题是一道经典的利用ID-DFS解决的一道搜索题目。开始做的时候看数据范围比较小,打算爆搜,发现第4个点就爆了。于是想改进方法。
1、二分枚举答案。可以用贪心法求出一个二分的下限,上限当然是rail的总个数。然后就是搜索对于当前的(l+r) div 2的深度(即可切得的rail的个数)是否可行。

2、排序。排序在这道题里面有相当大的用处。
    首先说一下顺序,board是从大到小,rail是从小到大。这样做的好处是:可以将一些没有用的board和rail在一开始就剪掉。因为比最小的rail还小的board和比最大的board还大的rail都是没有用的。
    其次,排序以后,可以利用有序的条件来规定搜索顺序。显然,board要从大到小用,rail要从小到大用,这样才能尽快到达目标状态。

3、如果当前可以砍出最小rail的board的总和比所有尚未砍出的rail的总和还要小------剪掉!

4、我们发现:rail一共有1023个,然而rail的最大值只有128,这就提示我们,有相当多的相同的rail。可以想到,如果rail[i]=rail[i+1],那么rail[i+1]所用的木料一定是rail[i]的后一个。

至此,每个点的时间都不超过0.1s 了。很爽吧    ^.^


 

?
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/*
ID:alertya1
PROG:fence8
LANG:C++
*/
#include <fstream>
#include <algorithm>
#include <set>
usingnamespace std;
ifstream fin("fence8.in");
ofstream fout("fence8.out");
intN,R,A[2048],Cf[60],Now[60],Pre[2048],Max,Sums,Limit,Tot;
boolflag;
boolCmp(inta,intb)
{
    returna>b;
}
voidInitialize()
{
inti;
    fin>>N;
    for(i=1;i<=N;i++)
    {
        fin>>Cf[i];
        Tot+=Cf[i];
    }
    sort(Cf+1,Cf+N+1);
    fin>>R;
    for(i=1;i<=R;i++)
        fin>>A[i];
    sort(A+1,A+R+1);
    for(i=1;i<=R;i++)
        if(A[i]>Cf[N])
            break;
    R=i-1;
    for(i=1;i<=R;i++)
        Pre[i]=Pre[i-1]+A[i];
}
 
voidDFS(intpos,intPrev)
{
inti,st,R=0;
    if(Sums>Tot-Pre[Limit])
        return;
    if(pos>Limit)
        flag=true;
    if(flag)
        return;
    st=1;
    if(A[Limit-pos+1]==A[Limit-pos+2])
        st=Prev;
    for(i=st;i<=N;i++)
        if(A[Limit-pos+1]<=Cf[i])
        {
            Cf[i]-=A[Limit-pos+1];
            if(Cf[i]<A[1])
                Sums+=Cf[i];
            DFS(pos+1,i);
            if(Cf[i]<A[1])
                Sums-=Cf[i];
            Cf[i]+=A[Limit-pos+1];
        }
}
 
intmain()
{
    Initialize();
     
    for(Limit=1;Limit<=R;Limit++)
    {
        flag=false;
        DFS(1,1);
        if(!flag)
            break;
    }
    fout<<Limit-1<<endl;
    fin.close();
    fout.close();
    return0;
}

描述
农民John准备建一个栅栏来围住他的牧场。他已经确定了栅栏的形状,但是他在木料方面有些问题。当地的杂货储存商扔给John一些木板,而John必须从这些木板中找出尽可能多所需的木料。

当然,John可以切木板。因此,一个9英尺的木板可以切成一个5英尺和一个4英尺的木料 (当然也能切成3个3英尺的,等等)。John有一把(完美的)梦之锯,因此他在切木料时,不会有木料的损失。

所需要的栅栏长度可能会有重复(比如,一个3英尺和另一个3英尺长的栅栏可能同时都需要)。所需要的木料规格都已经给定。你不必切出更多木料,那没有用。

 

格式

PROGRAM NAME: fence8

INPUT FORMAT:

(file fence8.in)

第1行: N (1 <= N <= 50), 表示提供的木板的数目

第2行到第N+1行: N行,每行包括一个整数,表示各个木板的长度。

第N+2行: R (1 <= R <= 1023), 所需木料的数目

第N+3行到第N+R+2行: R行,每行包括一个整数(1 <= ri <= 128)表示所需木料的长度。

OUTPUT FORMAT:

(file fence8.out)

只有一行,一个数字,表示能切出的最多的所需木料的数目。当然,并不是任何时候都能切出所有所需木料。

SAMPLE INPUT

4304050251015161718192021252430 SAMPLE OUTPUT  
7
 

USACO4.1 Fence rails DFSID

分类: USACO DFSID 149人阅读 评论(0) 收藏 举报
rails优化searchgoogle

所有题在A不掉之前都是难题,而当你把它A掉后,它便成了水题,而在你A掉它之前,你永远不知道它有多水。

这道题,我最先想到的是DP,觉得就是一个典型背包问题,不过是背包多了一点而已,然而再仔细一想N=50便意味着50个背包,128^50的运算量,不用细算也知道这样的程序跑出来太阳都熄火了,所以这样的DP是走不通的。想到这个section的主题是讲搜索优化的,那就试试搜索吧,直接DFS加剪枝?嗯,想了很久没想出来怎么做DFS,就在这个时候ZZY看完了刚更新的日剧,坐在旁边摇头晃脑满脸的意犹未尽,时不时往我这边漂上几眼,看到我一副苦逼的表情,点了几下头然后便笑而不语,过了许久才阴森的冒出一句话“这个要用DFSID”,我靠,什么是DFSID啊,完全不懂啊,听起来好高端啊...赶紧google之.....

搜索DFSID,wiki上没有,××没有,看了别人的博客,大致的感觉就是先确定一个界限再做DFS,而在这道题里面感觉倒有点像是枚举加DFS。在这个题中,用DFS找到能切割出的最大rail数不好怎么下手,但是,用DFS确定是否能切割出K块rail是相对容易的,所以我们就可以通过枚举出各个K值然后DFS得出能切割出的最大rail数,这里可以用二分的思想来做优化。

现在的主要问题就是如何DFS了。 既然是要确定一堆board能否切割出k块rail,我们不妨用一个全局的数组来储存各个board 的长度,用bool DFS(int k)来表示k块rail能否被切割出来,如果这堆board能切割出k块rail那么肯定能切割出k块最短的rail,所以,我们应该先给rail排个序。

朴素DFS代码:

[cpp] view plaincopy
  1. bool DFS(int k)  
  2. {   
  3.      if(k<0) return true;  
  4.      for(int i=0;i<boards;i++)  
  5.      {  
  6.           if(remain[i]>=rarr[k])//remain[i]记录了第i块board的剩余长度  
  7.           {  
  8.                 remain[i]-=rarr[k];  
  9.                 if(DFS(k-1)) return true;//先对rail排了序,rail从大到小搜  
  10.                 remain[i]+=rarr[k];  
  11.           }  
  12.      }  
  13.      return false;  
  14. }  
这样没有做任何优化的DFS 只能过3个点,所以动手优化吧。

在网上看了大牛们的博客,都说对于这样的切割问题,用剩余材料做优化可以减少大量的冗余搜索,其大致思路是这样的,如果boad的总长度是board_sum,需要切割出来的rail总长度是rail_sum,那么最大的可浪费材料是max_waste,如果某一种切割方式在切割完之前已产生了waste>max_waste的浪费,那么显然这种方式是不可行的。

优化后代码

[cpp] view plaincopy
  1. <pre name="code" class="cpp">bool DFS(int k)  
  2. {   
  3.      if(k<0) return true;</pre><pre name="code" class="cpp">     //用浪费材料做剪枝</pre><pre name="code" class="cpp">     int waste=0;</pre><pre name="code" class="cpp">     for(int i=0;i<boards;i++) if(remain[i]<rarr[0]) waste+=remain[i];  
  4.      if(waste>max_waste) return false;  
  5.      for(int i=start;i<boards;i++)  
  6.      {  
  7.           if(remain[i]>=rarr[k])  
  8.           {  
  9.                 if(k-1>=0 && rarr[k]==rarr[k-1]) ns=i;  
  10.                 remain[i]-=rarr[k];  
  11.                 if(DFS(k-1)) return true;  
  12.                 remain[i]+=rarr[k];  
  13.           }  
  14.      }  
  15.      return false;  
  16. }</pre><br>  
  17. <pre></pre>  
  18. <p></p>  
  19. <pre></pre>  
  20. <span style="white-space:pre"></span>优化后,竟然还是只能过3个点,太受打击了。  
  21. <p></p>  
  22. <p><span style="white-space:pre"></span>由于rail最多有1023块,而rail的长度是<=128的正整数,之意味着很多rail的长度是一样的,而对于长度一样的rail,它们的顺序是不重要的,所以可以做如下优化:</p>  
  23. <p><span style="white-space:pre"></span>优化后代码</p>  
  24. <p></p>  
  25. <pre name="code" class="cpp">bool DFS(int k,int start)  
  26. {   
  27.      if(k<0) return true;  
  28.      int waste=0;  
  29.      for(int i=0;i<boards;i++) if(remain[i]<rarr[0]) waste+=remain[i];  
  30.      if(waste>max_waste) return false;  
  31.      int ns=0;  
  32.      for(int i=start;i<boards;i++)  
  33.      {  
  34.           if(remain[i]>=rarr[k])  
  35.           {  
  36.                 if(k-1>=0 && rarr[k]==rarr[k-1]) ns=i;  
  37.                 else ns=0;  
  38.                 remain[i]-=rarr[k];  
  39.                 if(DFS(k-1,ns)) return true;  
  40.                 remain[i]+=rarr[k];  
  41.           }  
  42.      }  
  43.      return false;  
  44. }</pre><span style="white-space:pre"> </span>优化后提交,果断AC。  
  45. <p></p>  
  46. <p><span style="white-space:pre"></span>经实验发现,这两种优化缺少任何一种都只能过3个点,只有两种优化同时采用才能AC。</p>  
  47. <p><span style="white-space:pre"></span>下面是完整的源代码:</p>  
  48. <p></p>  
  49. <pre name="code" class="cpp">#include<cstdio>  
  50. #include<algorithm>  
  51.   
  52. using namespace std;  
  53.   
  54. FILE *in,*out;  
  55. int barr[60],remain[60],rarr[1050],rarr_sum[1050],max_waste,board_sum,boards;  
  56.   
  57. int search(int s,int e);  
  58. bool check(int k);  
  59. bool DFS(int k,int start);  
  60.   
  61. int main()  
  62. {  
  63.     in=fopen("fence8.in","r");  
  64.     out=fopen("fence8.out","w");  
  65.     int rails,i;  
  66.       
  67.     fscanf(in,"%d",&boards);  
  68.     for(i=0;i<boards;i++) fscanf(in,"%d",&barr[i]);  
  69.     for(i=0;i<boards;i++) barr[i]=-barr[i];  
  70.     sort(barr,barr+boards);  
  71.     for(i=0;i<boards;i++) barr[i]=-barr[i];  
  72.       
  73.     fscanf(in,"%d",&rails);  
  74.     for(i=0;i<rails;i++) fscanf(in,"%d",&rarr[i]);  
  75.     sort(rarr,rarr+rails);     
  76.   
  77.     for(i=0;i<boards;i++) board_sum+=barr[i];  
  78.     for(i=0;i<rails;i++) rarr_sum[i]=rarr[i];  
  79.     for(i=1;i<rails;i++) rarr_sum[i]+=rarr_sum[i-1];  
  80.     max_result=0;  
  81.     for(i=1;i<rails;i*=2) if(!check(i)) break;  
  82.     if(i>rails) i=rails;  
  83.     fprintf(out,"%d\n",search(i>>1,i));  
  84.     fclose(in);  
  85.     fclose(out);  
  86.     return 0;  
  87. }  
  88.   
  89. int search(int s,int e)  
  90. {  
  91.     int mid=(s+e)>>1;  
  92.     if(e-s==1) mid=e;  
  93.     bool flag=check(mid);  
  94.     if(e-s==1 && flag) return e;  
  95.     if(e-s==1 && !flag) return s;  
  96.     if(e==s && flag) return e;  
  97.     if(flag) return search(mid,e);  
  98.     else return search(s,mid);  
  99. }  
  100.   
  101. bool check(int k)  
  102. {  
  103.      if(k<=0) return true;  
  104.      for(int i=0;i<boards;i++) remain[i]=barr[i];  
  105.      max_waste=board_sum-rarr_sum[k-1];  
  106.      if(max_waste<0) return false;  
  107.      return DFS(k-1,0);  
  108. }  
  109.   
  110. bool DFS(int k,int start)  
  111. {   
  112.      if(k<0) return true;  
  113.      int waste=0;  
  114.      for(int i=0;i<boards;i++) if(remain[i]<rarr[0]) waste+=remain[i];  
  115.      if(waste>max_waste) return false;  
  116.      int ns=0;  
  117.      for(int i=start;i<boards;i++)  
  118.      {  
  119.           if(remain[i]>=rarr[k])  
  120.           {  
  121.                 if(k-1>=0 && rarr[k]==rarr[k-1]) ns=i;  
  122.                 else ns=0;  
  123.                 remain[i]-=rarr[k];  
  124.                 if(DFS(k-1,ns)) return true;  
  125.                 remain[i]+=rarr[k];  
  126.           }  
  127.      }  
  128.      return false;  
  129. }  
  130. </pre><br>  
  131. <br>  
  132. <p></p>  
原创粉丝点击