每日一题(19)――数组分割(动态规划) - 小熊不去实验室 - 博客频道 - CSDN.NET



一、问题:

      1. 有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为两个子数组,子数组的元素个数不限,并使两个子数组之和最接近。
      2. 有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组之和最接近。
解法1:(小田)
分为几个阶段:
       外阶段:在前k1个数中进行选择,k1=1,2...2*n。
       内阶段:从这k1个数中任意选出k2个数,k2=1,2...k1。
       状态:这k2个数的和为s,s=1,2...sum/2。
       决策:决定这k2个数的和有两种决策,一个是这k2个数中包含第k1个数,另一个是不包含第k1个数。
这种做法与0-1背包的方法2相似。很厉害的方法,不需要判断一个节点是否已经使用过
dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在。
  1. int main()  
  2. {  
  3.     int n, i, k1, k2, s, u;  
  4.     cin >> n;  
  5.     for (i=1; i<=2*n; i++)  
  6.         cin >> A[i];  
  7.     int sum = 0;  
  8.     for (i=1; i<=2*n; i++)  
  9.         sum += A[i];  
  10.     memset(dp,0,sizeof(dp));  
  11.     dp[0][0]=true;  
  12.         // 外阶段k1表示第k1个数,内阶段k2表示选取数的个数  
  13.          // 这跟前面陪审团和0-1背包的方法不太一样,他们是在外阶段(外循环)迭代选取个数,内阶段迭代具体选取那个数  
  14.          // 这样做需验证选取的数是否出现过,但是可以通过保存Path[个数][状态和]来存储各个状态;  
  15.     for (k1=1; k1<=2*n; k1++)            // 外阶段k1  
  16.     {  
  17.         for (k2=k1; k2>=1; k2--)     // 内阶段k2  
  18.             for (s=1; s<=sum/2; s++) // 状态s  
  19.             {  
  20.                 //dp[k1][s] = dp[k1-1][s];  
  21.                 // 有两个决策包含或不包含元素k1  
  22.                 if (s>=A[k1] && dp[k2-1][s-A[k1]])  
  23.                     dp[k2][s] = true;  
  24.             }  
  25.     }  
  26.     /*根据0-1背包问题改写的方法:事实证明这种方法在不判断选用节点k2是否使用过的情况下,不可取,因为可能会重复调用某一个节点,除非再利用Path[k1][s]保存相应状态的节点。再判断它是否出过。那样的话,就需要用dp[k1][s]保存状态和s,也就是说跟第二个坐标一样。 
  27.     for (k1=0; k1<2*n; k1++)         // 迭代选取数量 
  28.     { 
  29.         for (s=0; s<=sum/2; s++)         // 状态和sum:s 
  30.             if(dp[k1][s]==true) 
  31.             for (k2=1; k2<=2*n; k2++)        // 选取第k2个    
  32.             { 
  33.                 if (s>=A[k1] )   // if(s>=A[k1] && dp[k2-1][s-A[k1]]) 
  34.                     dp[k1+1][s+A[k2]] = true; 
  35.             } 
  36.     } 
  37.     */  
  38.     // 之前的dp[k][s]表示从前k个数中取任意k个数,经过下面的步骤后  
  39.     // 即表示从前k个数中取任意个数  
  40.     for (k1=2; k1<=2*n; k1++)  
  41.         for (s=1; s<=sum/2; s++)  
  42.             if (dp[k1-1][s])  
  43.                 dp[k1][s]=true;  
  44.     // 确定最接近的给定值sum/2的和  
  45.     for (s=sum/2; s>=1 && !dp[2*n][s]; s--)  
  46.                ;  
  47.                  
  48.     printf("the differece between two sub array is %d\n", sum-2*s);  
  49. }  
解法2.
根据0-1背包问题方法2改写的算法:也好使~这种方式更简便!
只需要改动一点:利用dp[i][m]保存状态和,跟m的值一样。
依然无需测试一个节点是否已经存在,但依然无法保存路径,即无法输出最优解的过程
  1. struct{  
  2.     int wei;  
  3. }node[nMax];  
  4.   
  5. int main()  
  6. {  
  7.     int m, n, i ,w ,dp[mMax],sumN=0;  
  8.     while(cin>>n)  
  9.     {  
  10.         if(n==0) break;  
  11.         for (i=1; i<=2*n;i++)  
  12.         {  
  13.             cin>>node[i].wei;  
  14.             sumN += node[i].wei;  
  15.         }  
  16.         m=sumN/2;  
  17.   
  18.         memset(dp, 0, (m+1)*sizeof(int));  
  19.   
  20.         for (i=1; i<=2*n; i++)  
  21.             for ( w=m; w>=node[i].wei; w-- )//根据解法1的分析,必须将权重保存成下标才能满足最优化问题。  
  22.                 if ( dp[w] < dp[w - node[i].wei] + node[i].wei )  
  23.                     dp[w] = dp[w - node[i].wei] + node[i].wei;  
  24.   
  25.         cout<<dp[m]<<endl;  
  26.         cout<<"the differece between two sub array is: "<<sumN - 2*dp[m]<<endl;  
  27.     }  
  28. }
解法3,
根据公正陪审团问题改写的算法dp[k][s]:外循环是选取个数k,中间是状态和s,内循环是选取具体哪一个index;同时可以保存路径,输出具体选取哪些数字
  1. int A[MAXN];  
  2. int dp[MAXN][MAXSUM];  
  3. int Path[MAXN][MAXSUM];  
  4. // dp[k][s]表示取k个数,且和为s的情况下,保存的依然是和s;因为要优化判断  
  5. int main()  
  6. {  
  7.     int n, i, k1, k2, s, u,t1,t2;  
  8.     cin >> n;  
  9.     for (i=1; i<=2*n; i++)  
  10.         cin >> A[i];  
  11.     int sum = 0;  
  12.     for (i=1; i<=2*n; i++)  
  13.         sum += A[i];  
  14.     memset(dp,-1,sizeof(dp));  
  15.       
  16.     dp[0][0]=0;//初始状态  
  17.   
  18.     for (k1=0; k1<2*n; k1++)         // 选取的数量k1  
  19.     {  
  20.         for (s=0; s<=sum/2; s++)     // 状态和s  
  21.         if(dp[k1][s]>=0)  
  22.         {  
  23.             for (k2=1; k2<=2*n; k2++)        // 具体选取哪一个k2  
  24.                 if(dp[k1][s]+A[k2]>dp[k1+1][s+A[k2]] && s+A[k2]<=sum/2)  
  25.                 {  
  26.                     t1=k1;t2=s;  
  27.                     while(t1>0&&Path[t1][t2]!=k2)//验证k2是否在前面出现过  
  28.                     {  
  29.                         t2-=A[Path[t1][t2]] ;//减前一个元素的值  
  30.                         t1--;  
  31.                     }  
  32.                     if (t1==0)  
  33.                     {  
  34.                         dp[k1+1][s+A[k2]] = dp[k1][s]+A[k2];  
  35.                         Path[k1+1][s+A[k2]] = k2;       //k2保存在Path中  
  36.                     }  
  37.                 }  
  38.         }  
  39.     }  
  40.   
  41.     int maxS=0,maxN=0;  
  42.     for (k1=1; k1<=2*n; k1++)  
  43.         for (s=1; s<=sum/2; s++)  
  44.             if (dp[k1][s]>maxS)  
  45.             {  
  46.                 maxS=dp[k1][s];  
  47.                 maxN=k1;  
  48.             }  
  49.     cout<<"the count of num: "<<maxN<<"  the max sum of the num: "<<maxS<<endl;         
  50.     cout<<"the differece between two sub array is: "<< sum-2*maxS<<endl;  
  51.   
  52.     set<int> index;  
  53.     index.clear();  
  54.     for (int i=0; i<maxN; i++)  
  55.     {  
  56.         int id = Path[maxN-i][maxS];  
  57.         index.insert(id);  
  58.         maxS -= A[id];  
  59.     }  
  60.     cout<<endl;  
  61.     cout<<"the index of selected num: ";  
  62.     for(set<int>::iterator iter=index.begin(); iter!=index.end(); iter++) cout<<*iter<<" ";   
问题2.
解法1:
     但本题还增加了一个限制条件,即选出的物体数必须为n,这个条件限制了内阶段k2的取值范围,(同:公正陪审团问题)并且dp[k][s]的含义也发生变化。这里的dp[k][s]表示从前k个数中取任意不超过n的k个数,且这些数之和为s的取法是否存在
  1. #define MAXN 101  
  2. #define MAXSUM 100000  
  3. int A[MAXN];  
  4. bool dp[MAXN][MAXSUM];  
  5. // 题目可转换为从2n个数中选出n个数,其和尽量接近于给定值sum/2  
  6. int main()  
  7. {  
  8.     int n, i, k1, k2, s, u;  
  9.     cin >> n;  
  10.     for (i=1; i<=2*n; i++)  
  11.         cin >> A[i];  
  12.     int sum = 0;  
  13.     for (i=1; i<=2*n; i++)  
  14.         sum += A[i];  
  15.     memset(dp,0,sizeof(dp));  
  16.     dp[0][0]=true;  
  17.     // 对于dp[k][s]要进行u次决策,由于阶段k的选择受到决策的限制,  
  18.     // 这里决策选择不允许重复,但阶段可以重复,比较特别  
  19.     for (k1=1; k1<=2*n; k1++)                // 外阶段k1  
  20.         for (k2=min(k1,n); k2>=1; k2--)      // 内阶段k2  
  21.             for (s=1; s<=sum/2; s++) // 状态s  
  22.                 // 有两个决策包含或不包含元素k1  
  23.                 if (s>=A[k1] && dp[k2-1][s-A[k1]])  
  24.                     dp[k2][s] = true;  
  25.     // 确定最接近的给定值sum/2的和  
  26.     for (s=sum/2; s>=1 && !dp[n][s]; s--);  
  27.     printf("the differece between two sub array is %d\n", sum-2*s);  
问题1的解法3肯定适用,只要最后选择最大值时确定dp[k][s]的k即可;
问题公正陪审团问题1的解法2是否使用,还有待验证。

注意:如果数组中有负数的话,上面的背包策略就不能使用了(因为第三重循环中的s是作为数组的下标的,不能出现负数的),需要将数组中的所有数组都加上最小的那个负数的绝对值,将数组中的元素全部都增加一定的范围,全部转化为正数,然后再使用上面的背包策略就可以解决了。
这种带权重的求和的最优化问题,都可以转换为数组求和最优问题,(转化为判别划分问题)。
从具体的解法上来说,解法3,即公正陪审团问题的解法比较完善,可以保存路径,但是需要额外判断选取的某个数是否已经存在。且这种解法比较容易理解:
dp[k][s]:
外循环迭代k,表示取元素的个数;
中间循环迭代s:表示状态和s(限制条件,不能超过多少……)
内循环迭代index:表示具体是否选取A[index];
判别:+A[index]满足最优条件 &A[index]没有出现过(通过Path判别,保存)
最终:输出时若有个数限制K,则在dp[K][]中查找,若没有个数限制,则在dp[][]全部中查找
PS:利用Path输出路径时,可以先保存在set中,这样输出有序。
Read full article from 每日一题(19)——数组分割(动态规划) - 小熊不去实验室 - 博客频道 - CSDN.NET

No comments:

Post a Comment

Labels

Algorithm (219) Lucene (130) LeetCode (97) Database (36) Data Structure (33) text mining (28) Solr (27) java (27) Mathematical Algorithm (26) Difficult Algorithm (25) Logic Thinking (23) Puzzles (23) Bit Algorithms (22) Math (21) List (20) Dynamic Programming (19) Linux (19) Tree (18) Machine Learning (15) EPI (11) Queue (11) Smart Algorithm (11) Operating System (9) Java Basic (8) Recursive Algorithm (8) Stack (8) Eclipse (7) Scala (7) Tika (7) J2EE (6) Monitoring (6) Trie (6) Concurrency (5) Geometry Algorithm (5) Greedy Algorithm (5) Mahout (5) MySQL (5) xpost (5) C (4) Interview (4) Vi (4) regular expression (4) to-do (4) C++ (3) Chrome (3) Divide and Conquer (3) Graph Algorithm (3) Permutation (3) Powershell (3) Random (3) Segment Tree (3) UIMA (3) Union-Find (3) Video (3) Virtualization (3) Windows (3) XML (3) Advanced Data Structure (2) Android (2) Bash (2) Classic Algorithm (2) Debugging (2) Design Pattern (2) Google (2) Hadoop (2) Java Collections (2) Markov Chains (2) Probabilities (2) Shell (2) Site (2) Web Development (2) Workplace (2) angularjs (2) .Net (1) Amazon Interview (1) Android Studio (1) Array (1) Boilerpipe (1) Book Notes (1) ChromeOS (1) Chromebook (1) Codility (1) Desgin (1) Design (1) Divide and Conqure (1) GAE (1) Google Interview (1) Great Stuff (1) Hash (1) High Tech Companies (1) Improving (1) LifeTips (1) Maven (1) Network (1) Performance (1) Programming (1) Resources (1) Sampling (1) Sed (1) Smart Thinking (1) Sort (1) Spark (1) Stanford NLP (1) System Design (1) Trove (1) VIP (1) tools (1)

Popular Posts