posts - 403, comments - 310, trackbacks - 0, articles - 7
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          PKU 1014 Dividing

          Posted on 2007-07-30 19:59 ZelluX 閱讀(2783) 評論(3)  編輯  收藏 所屬分類: Algorithm
          把總長度求出來,然后用和背包問題類似的dp算法求是否可以達到這個總長度的一半,但是超時了。
          找到一個優化方案,結果0ms就過了,orz
          http://162.105.81.202/course/problemSolving/dividingProve.doc
          以下論證均轉自這篇文章。
           結論 對于任意一種珠寶的個數n,如果n>=8, 可以將n改寫為 11(n為奇數) 或 12(n為偶數)。

          證明:

          對于任意一組數,6的個數為n(n>=8)

          一、如果可以分成兩堆,我們可以分成兩種情況:
             1.
                兩堆里都有6,那么我們可知:把n改為n-2,仍然可分。
          (兩堆各減一個6)
          2. 只有一堆里有6,設為左邊,那么左邊的總和不小于6*8=48。
          我們觀察,5*6=6*5 ,4*3=6*2 , 3*2=6 , 2*3=6 , 1*6=6
          而 5*5 + 4*2 + 3*1 + 2*2 + 1*5 = 25 + 8 + 3 + 4 + 5 = 45 < 48
          由抽屜原理右邊必然存在
          (多于5個的5 或者 多于2個的4 或者 多于1個的3
          或者 多于2個的2 或者 多于5個的1)
          即右邊至少存在一組數的和等于若干個6,比如右邊有3個4,這樣把左邊的2個6與右邊的3個4交換,則又出現左右都有6的情況。 根據1,我們還是可以把n改為n-2且可分的狀態不變。
          綜合1,2。我們可以看出只要原來n的個數=8,我們就可以把它改為n-2,這樣操作一直進行到n<8。我們可以得出結論,對于大于等于8的偶數,可以換成6。
          對于大于8的奇數,可以換成7。換完之后仍然可分。

          二、如果不能分成兩堆:
          顯然改為n-2時同樣也不能分,那么對于大于等于8的偶數,可以換成6;對于大于8的奇數,可以換成7。換完之后仍然不可分。

          綜合一、二,我們得出結論把不小于8的偶數改為8,大于8的奇數改為7,原來可分與否的性質不會改變。

          以上是對6的討論,同樣的方法可以推出
          5的個數 6*4 + 4*4 + 3*4 + 2*4 + 1*4 = 64 < 5*13
          即5的個數多于12時,偶數換為12,奇數換為11
          4的個數 6*1 + 5*3 + 3*3 + 2*1 + 1*3 = 35 < 4*9
          即4的個數多于8時,偶數換為8,奇數換為7
          3的個數 5*2 + 4*2 + 2*2 + 1*2 = 24 < 3*9
          即3的個數多于8時,偶數換為8,奇數換為7
          2的個數 5*1 + 3*1 + 1*1 = 9 < 2*5
          即2的個數多于4時,偶數換為4,奇數換為3
          1的個數 多于5則必然可分(在總數是偶數的前提下)

          綜上所述,
          對于任意一種珠寶的個數n,如果n>=8, 可以將n改寫為 11(n為奇數) 或 12(n為偶數)。

          進一步分析:
          對每個數(1-6),以上只是粗略的估計,可以進一步減少其最大有效取值,例如,
          對于6,5*5 + 4*2 + 3*1 + 2*2 + 1*5 = 25 + 8 + 3 + 4 + 5 = 45
          就有4和2不能同時出現,5和1不能同時出現,3個5和1個3不能同時出現,4個5不能和1個4同時出現等等,所以組合不出6的整數倍的情況的總價值至多為25,所以當6的個數大于6時,奇數可改為5,偶數可改為6。
          1-5 也有類似情況。

          為了得出精確值,下面先我們討論這樣一個數論命題。

          命題:
          可重復的從自然數集中取出n個數(n>=2),其中必有若干個數之和能被n整除。

          證明:設取出的n個自然數為a1,a2,a3,.....an

          考慮這樣的n+1個數 0, a1, a1+a2 , a1+a2+a3 , ...... , a1+a2+a3+...+an, 由于自然數模n的剩余類有n個,所以以上n+1個數中必有兩個同余。 這兩個數的差必被n整除,而且這兩個數的差就是原來的n個數中的一些數的和。
          這就證明了命題。

          由以上命題
          對于6而言,我們至多從{1,2,3,4,5}中可重復的找出5個數使它們不能組合成6的倍數。
          所以這些數的和小于等于5*5=25
          對于5而言,我們至多從{1,2,3,4,6}中可重復的找出4個數使它們不能組合成5的倍數。
          所以這些數的和小于等于6*4=24
          對于4而言,我們至多從{1,2,3,5,6}中可重復的找出3個數使它們不能組合成4的倍數。
          所以這些數的和小于等于3*6=18 , 然而,兩個6就是4的倍數, 所以最多有一個6
          此時不能有兩個5(2*5+6=16是4的倍數), 最多才6 + 5 + 3 = 14 < 3*5 =15
          所以這些數的和小于等于3*5=15
          對于3而言,我們至多從{1,2,4,5,6}中可重復的找出2個數使它們不能組合成3的倍數。
          所以這些數的和小于等于2*5=10

          (6就是3的倍數,所以不能取6)

          對于2而言,我們至多從{1,3,4,5,6}中可重復的找出1個數使它們不能組合成6的倍數。

          所以這些數的和小于等于1*5=5



          考慮到 4*6 < 25 < 5*6 , 我們可以算出6的最大有效個數為5 。

          考慮到 4*5 < 24 < 5*5 , 我們可以算出5的最大有效個數為5 。但是其實應該修正為6, 如果遇到如下特殊情況,左邊5個6,右邊6個5。此時雖然左右可以交換,但是交換后仍然只有一邊有5,與(一、2)中討論情況不符。

          考慮到 3*4 < 15 < 4*4 , 我們可以算出5的最大有效個數為4 。但是其實應該修正為5, 如果遇到如下特殊情況,左邊4個5,右邊5個4。此時雖然左右可以交換,但是交換后仍然只有一邊有4,與(一、2)中討論情況不符。

          考慮到 3*3 < 10 < 4*3 , 我們可以算出5的最大有效個數為4 。但是其實應該修正為5, 如果遇到如下特殊情況,左邊3個5,右邊5個3。此時雖然左右可以交換,但是交換后仍然只有一邊有3,與(一、2)中討論情況不符。

          考慮到 2*2 < 5 < 3*2 , 我們可以算出5的最大有效個數為3 。 但是其實應該修正為4,如果遇到如下特殊情況,左邊1個3和1個5,右邊4個2。此時雖然左右可以交換,但是交換后仍然只有一邊有2,與(一、2)中討論情況不符。


          我們得出最后的精確結論:

          奇數改為 偶數改為
          6的個數大于5 5 4
          5的個數大于6 5 6
          4的個數大于5 5 4
          3的個數大于5 5 4
          2的個數大于4 3 4 



          優化后的代碼:
          #include <iostream>
          using namespace std;

          long n[6];
          long sum;
          const long MAX_N = 60000;

          int dividable()
          {
              
          int f[MAX_N];
              
          for (int i = 0; i <= sum; i++)
                  f[i] 
          = 0;
              f[
          0= 1;
              
          for (int i = 0; i < 6; i++)
              {
                  
          for (int j = 1; j <= n[i]; j++)
                  {
                      
          int base = j * (i + 1);
                      
          if (base > sum) break;
                      
          for (int k = sum - (i+1); k >= base - i - 1; k--)
                          
          if (f[k])
                              f[k 
          + i + 1= 1;
                      
          if (f[sum]) return 1;
                  }
              }
              
          return f[sum];
          }

          int main()
          {
              
          long cases = 0;
              
          while (true)
              {
                  sum 
          = 0;
                  
          for (long i = 0; i < 6; i++)
                  {
                      cin 
          >> n[i];
                  }
                  
          if (n[5> 5) n[5= 4 + n[5% 2;
                  
          if (n[4> 6) n[4= 6 - n[4% 2;
                  
          if (n[3> 5) n[3= 4 + n[3% 2;
                  
          if (n[2> 5) n[2= 4 + n[2% 2;
                  
          if (n[1> 4) n[1= 4 - n[1% 2;
                  
          for (long i = 0; i < 6; i++)
                  {
                      sum 
          += n[i] * (i + 1);
                  }
                  
          if (sum == 0)
                      
          break;
                  cases
          ++;
                  cout 
          << "Collection #" << cases << ":\n";
                  
          if (sum % 2 != 0)
                  {
                      cout 
          << "Can't be divided.\n\n";
                      
          continue;
                  }
                  sum 
          /= 2;
                  
          if (dividable())
                      cout 
          << "Can be divided.\n";
                  
          else
                      cout 
          << "Can't be divided.\n";
                  cout 
          << endl;
              }
              
          return 0;
          }



          評論

          # re: PKU 1014 Dividing  回復  更多評論   

          2007-09-02 20:53 by 流牛木馬
          竟然被我找到了!
          你這算法太好了~
          動歸不會超時,有明顯的剪枝嘛。
          看這個動歸程序是AC的。


          #include<stdio.h>
          bool opt[60000];
          int num=0;
          int mid,max;
          int stone[7];
          int input()
          {
          scanf("%d%d%d%d%d%d",&stone[1],&stone[2],&stone[3],&stone[4],&stone[5],&stone[6]);
          if(!stone[6]&&!stone[1]&&!stone[2]&&!stone[3]&&!stone[4]&&!stone[5]) {return 1;} //讀到末行
          num++;
          printf("Collection #%d:\n",num);
          mid=0;
          for(int i=1;i<=6;i++) mid+=stone[i]*i;
          if (mid % 2 ==1) //明顯的剪枝
          {
          printf("Can't be divided.\n\n");
          return 2;
          }
          else mid/=2; //我們的任務就是求opt
          return 0;
          }

          void dp()
          {
          memset(opt,0,60000); //初始化,opt所有成員為false
          opt[0]=true; //opt[0]顯然是true
          max=0; //當前最大值是0

          for (int i=1;i<=6;i++)
          {
          if (stone[i]>0)
          {
          for(int j=max;j>=0;j--) // 從當前最大值開始往下算
          if (opt[j]) //找到可行的j
          {
          if (opt[mid]) //判斷是否已經求出結果
          {
          printf("Can be divided.\n\n");
          return;
          }
          for (int k=1;k<=stone[i];k++) //在剛找到的可行j基礎上加石頭.
          {
          if (j+k*i>mid || opt[j+k*i]) break; //如果已經大于總價值的一半mid,或opt[j+k*i]已計算,跳出循環
          opt[j+k*i]=true;
          }
          }
          }
          max=max+i*stone[i]; //更新當前最大值
          if (max>mid) max=mid; //如果當前最大值超過了mid,對其賦值為mid
          }
          printf("Can't be divided.\n\n"); //如果從1到6循環完一遍后仍然沒有算出opt[mid],說明無解
          }

          int main()
          {
          while (1)
          {
          int t=input();
          switch (t)
          {
          case 1 : return 0; //讀到末行,結束
          case 2 : continue; //讀到明顯的剪枝
          default : dp();
          }
          }
          return 0;
          }

          QQ 27402040 希望可以交個朋友

          # re: PKU 1014 Dividing  回復  更多評論   

          2010-01-02 10:50 by Giggs
          “考慮到 2*2 < 5 < 3*2 , 我們可以算出2的最大有效個數為3 。 但是其實應該修正為4,如果遇到如下特殊情況,左邊1個3和1個5,右邊4個2。此時雖然左右可以交換,但是交換后仍然只有一邊有2,與(一、2)中討論情況不符。”
          如果左邊2個5,右邊5個2,此時雖然左右可以交換,但是交換后仍然只有一邊有2。
          ∴2的最大有效個數為什么不修正為5?
          謝!

          # re: PKU 1014 Dividing  回復  更多評論   

          2011-04-09 10:05 by hy
          主站蜘蛛池模板: 九龙县| 镇原县| 公安县| 泸溪县| 垣曲县| 沁阳市| 修文县| 晴隆县| 景谷| 汶川县| 察隅县| 祁连县| 仁寿县| 湛江市| 临澧县| 崇州市| 邓州市| 闻喜县| 瓮安县| 黑水县| 灌云县| 库车县| 临桂县| 永清县| 大邑县| 买车| 莲花县| 淳化县| 车险| 阿巴嘎旗| 章丘市| 南部县| 张家川| 津南区| 平陆县| 金昌市| 瓮安县| 吉安县| 寿光市| 吉林省| 观塘区|