poj1015动态规划

选择陪审员 POJ1015 Jury Compromise 动态规划DP 搜索DFS 贪心

分类: POJ 61人阅读 评论(0) 收藏 举报

        感觉比较难的一题,即使做第三遍也感觉比较吃力。题目描述很清楚,英文理解无障碍。此题实质:n个物体中选m个。

        最容易想到的当然是搜索,即DFS。从1到n依次进行扫描,判断是否被选中。每选够m个,就与前面的结果进行一次比较。因为Max( n ) = 200,Max( m ) = 20所以搜索次数大致有 ,这样的规模应该比较恐怖。因此,如果不优化,暴搜必挂无疑。如何剪枝?也就是哪些状态不用搜索,可以直接跳过。例如,我们要在20个人中选10个人。在以前的搜索中,已经取得了最优解S。当前已经选择了5个人,如果按照目前的情况无论如何也不可能取到比S更优的解,这时就可以直接放弃,没有必要继续搜索剩下的5人。当然,按照这种思路可以剪枝。但是,算法本省已确定的了它的复杂度是指数级,剪枝能修改的只是系数。因此,意义不大。

  1. #include <iostream>   
  2. #include <cmath>   
  3. using namespace std;  
  4.   
  5. //***********************常量定义*****************************   
  6.   
  7. const int NUM = 205;  
  8. const int INF_MIN = -999999999;  
  9. const int INF_MAX = 999999999;  
  10.   
  11. //*********************自定义数据结构*************************   
  12.   
  13.   
  14.   
  15.   
  16. //********************题目描述中的变量************************   
  17.   
  18. int n, m;  
  19. int dValue[NUM];  
  20. int pValue[NUM];  
  21.   
  22.   
  23. //**********************算法中的变量**************************   
  24.   
  25. bool tmpUsed[NUM];  
  26. bool used[NUM];  
  27. int sub = INF_MAX;  
  28. int sum = INF_MIN;  
  29.   
  30.   
  31. //***********************算法实现*****************************   
  32.   
  33. void DFS( int curNum, int curId, int sumD, int sumP )  
  34. {  
  35.     int x = abs(sumD-sumP);       
  36.     int y = sumD+sumP;    
  37.   
  38.     if( curNum == m )  
  39.     {         
  40.         if( x < sub  || ( x == sub && y > sum ) )  
  41.         {  
  42.             memcpy( used, tmpUsed, sizeof(used) );                    
  43.             sub = x;                      
  44.             sum = y;              
  45.         }         
  46.         return;  
  47.     }  
  48.       
  49.     //此处可剪枝   
  50.     //if( ??? ) return;   
  51.   
  52.     int i;  
  53.     for( i=curId; i<n; i++ )  
  54.     {  
  55.         if( !tmpUsed[i] )  
  56.         {  
  57.             tmpUsed[i] = true;  
  58.             DFS( curNum+1, curId+1, sumD+dValue[i], sumP+pValue[i] );     
  59.             tmpUsed[i] = false;  
  60.         }                     
  61.     }     
  62. }  
  63.   
  64.   
  65. //************************main函数****************************   
  66.   
  67. int main()  
  68. {  
  69.     freopen( "in.txt""r", stdin );          
  70.       
  71.     int caseNum = 0;  
  72.     while( cin >> n >> m, !( n == 0 && m == 0 ) )  
  73.     {  
  74.         forint i=0; i<n; i++ )  
  75.         {  
  76.             cin >> pValue[i] >> dValue[i];  
  77.         }  
  78.   
  79.         sub = INF_MAX;  
  80.         sum = INF_MIN;  
  81.         memset( tmpUsed, falsesizeof(tmpUsed) );  
  82.         memset( used, falsesizeof(used) );  
  83.         DFS( 0, 0, 0, 0 );  
  84.   
  85.         int ansD = 0;  
  86.         int ansP = 0;         
  87.         forint j=0; j<n; j++ )  
  88.         {  
  89.             if( used[j] )  
  90.             {  
  91.                 ansD += dValue[j];  
  92.                 ansP += pValue[j];  
  93.             }  
  94.         }  
  95.         cout << "Jury #" << ++caseNum << endl;  
  96.         cout << "Best jury has value " << ansP << " for prosecution and value " << ansD << " for defence:" << endl;   
  97.         forint k=0; k<n; k++ )  
  98.         {  
  99.             if( used[k] )   cout << " " << k+1;  
  100.         }     
  101.         cout << endl;  
  102.     }     
  103.     return 0;  
  104. }  


 

        从动态规划出发,继续考虑。现在要在n个人中选出m个人,设所有人的编号:1…n。如果编号为n的人为被选中,那么问题转化为:在剩下的n-1个人中选m个。如果编号为n的人被选中,那么问题转化为:在剩下的n-1个人中选m-1个。

那么,编号为n的人到底是选还是不选?就要比较这两种方案,看那个更优了。

即,DP的状态转换方程:

DP[m,n,step]=Best(DP[m-1,n-1,step+dn-pn],DP[m,n-1,step])
step=sum(d)-sum(p).
Best(a,b)=(|a.d-a.p|<|b.d-b.q|)or((|a.d-a.p|==|b.d-b.q|)&&(a.d+a.p>b.d+b.q))?a:b.

        DP比递归的优势在于打表,如何打表避免重复计算?在程序中用到flag[25][205][805]。这里相当于枚举D(j) – P(j), 每一个值对应数组的一个元素。范围[-m*20,m*20]--->[0,m*20*2],故数组大小选805。Flag[m][n][v],标记n个中选m个,且D(j) – P(j) +400的值为V的状态是否已计算过。

  1. Source Code  
  2.   
  3. Problem: 1015  User: 3109034010   
  4. Memory: 31696K  Time: 266MS   
  5. Language: C++  Result: Accepted   
  6.   
  7. Source Code   
  8. #include <stdio.h>   
  9. #include <memory.h>   
  10.    
  11.   
  12. int Sum_d[25],Sum_p[25],jury_d[205],jury_p[205];  
  13.   
  14. int Best_d[25][205][805],Best_p[25][205][805];   
  15.   
  16. char flag[25][205][805],choose[25][205][805];  
  17.   
  18. int ans_jury[205];  
  19.   
  20.    
  21.   
  22.    
  23.   
  24. int abs(int num)//返回num的绝对值   
  25. {  
  26.         if(num<0)  
  27.                return -num;  
  28.   
  29.         else  
  30.                return num;  
  31.   
  32. }  
  33.    
  34.   
  35. int DP(int n,int m,int& dp_d,int& dp_p)  
  36. {  
  37.         int yes_d,yes_p,no_d,no_p,tm_d=dp_d,tm_p=dp_p;  
  38.   
  39.         int tm1,tm2;     
  40.   
  41.         if(m==0)  
  42.         {  
  43.                return 0;  
  44.   
  45.         }//已经选够人   
  46.         if(m==n)  
  47.         {  
  48.                dp_d+=Sum_d[m];  
  49.   
  50.                dp_p+=Sum_p[m];  
  51.   
  52.                return 0;  
  53.   
  54.         }//在a个人里选a个。。   
  55.         if(flag[m][n][400+tm_d-tm_p])  
  56.         {  
  57.    
  58.   
  59.                dp_d+=Best_d[m][n][400+tm_d-tm_p];  
  60.   
  61.                dp_p+=Best_p[m][n][400+tm_d-tm_p];  
  62.   
  63.                return 0;  
  64.   
  65.         }//已经算过的状态。   
  66.         flag[m][n][400+dp_d-dp_p]=1;  
  67.   
  68.    
  69.   
  70.         no_d=dp_d;  
  71.   
  72.         no_p=dp_p;  
  73.   
  74.         DP(n-1,m,no_d,no_p);//选择n   
  75.    
  76.   
  77.         yes_d=dp_d+jury_d[n];  
  78.   
  79.         yes_p=dp_p+jury_p[n];  
  80.   
  81.         DP(n-1,m-1,yes_d,yes_p);//抛弃n   
  82.    
  83.   
  84.         tm1=abs(yes_d-yes_p)-abs(no_d-no_p);  
  85.   
  86.         tm2=(yes_d+yes_p)-(no_d+no_p);  
  87.   
  88.    
  89.   
  90.         if((tm1<0)||((tm1==0)&&(tm2>0)))  
  91.         {  
  92.                dp_d=yes_d;  
  93.   
  94.                dp_p=yes_p;  
  95.   
  96.                choose[m][n][400+tm_d-tm_p]=1;  
  97.   
  98.         }  
  99.         else  
  100.         {  
  101.                dp_d=no_d;  
  102.   
  103.                dp_p=no_p;  
  104.   
  105.                choose[m][n][400+tm_d-tm_p]=0;  
  106.   
  107.         }//取最优解   
  108.         Best_d[m][n][400+tm_d-tm_p]=dp_d-tm_d;  
  109.   
  110.         Best_p[m][n][400+tm_d-tm_p]=dp_p-tm_p;//记录状态   
  111.         return 0;  
  112.   
  113. }  
  114.    
  115.   
  116. int doSearch(int m,int n,int pos)//搜索培训团组合。   
  117. {  
  118.         int i;  
  119.   
  120.         if(m==0)  
  121.                return 0;  
  122.   
  123.         if(m==n)  
  124.         {  
  125.                for(i=1;i<=n;i++)  
  126.                        ans_jury[i]++;  
  127.   
  128.                return 0;  
  129.   
  130.         }  
  131.         if(choose[m][n][400+pos]==1)  
  132.         {  
  133.                ans_jury[n]++;  
  134.   
  135.                doSearch(m-1,n-1,pos+jury_d[n]-jury_p[n]);  
  136.   
  137.         }  
  138.         else  
  139.         {  
  140.                doSearch(m,n-1,pos);  
  141.   
  142.         }  
  143.         return 0;  
  144.   
  145. }  
  146.    
  147.   
  148. int main()  
  149. {  
  150.         int i,m,n,t=0;  
  151.   
  152.         int ans_d=0,ans_p=0;  
  153.   
  154.    
  155.   
  156.         while(scanf("%d%d",&n,&m),m+n>0)  
  157.         {  
  158.                t++;  
  159.   
  160.                Sum_d[0]=0;  
  161.   
  162.                Sum_p[0]=0;      
  163.   
  164.                ans_d=0;  
  165.   
  166.                ans_p=0;  
  167.   
  168.                memset(flag,0,sizeof(flag));  
  169.   
  170.                memset(choose,0,sizeof(choose));  
  171.   
  172.                memset(ans_jury,0,sizeof(ans_jury));  
  173.   
  174.                for(i=1;i<=n;i++)  
  175.                {  
  176.                        scanf("%d%d",&jury_d[i],&jury_p[i]);  
  177.   
  178.                        if(i<=m)  
  179.                        {  
  180.                                Sum_d[i]=Sum_d[i-1]+jury_d[i];  
  181.   
  182.                                Sum_p[i]=Sum_p[i-1]+jury_p[i];  
  183.   
  184.                        }  
  185.                }  
  186.    
  187.   
  188.                DP(n,m,ans_d,ans_p);  
  189.   
  190.                doSearch(m,n,0);  
  191.   
  192.                printf("Jury #%d\nBest jury has value %d for prosecution and value %d for defence:\n",t,ans_d,ans_p);  
  193.   
  194.    
  195.   
  196.                for(i=1;i<=n;i++)  
  197.                {  
  198.                        if(ans_jury[i])  
  199.                                printf(" %d",i);  
  200.   
  201.                }  
  202.                printf("\n\n");  
  203.   
  204.         }  
  205.         return 0;  
  206. }  


 

        网上还有另外一种解法,枚举D(j) – P(j)。要选m个人,使|D(j) – P(j)|最小。如果已选了m-1个人,再选1个人即可。选的这一个人,只要使m个人的|D(j) – P(j)|最小即可。

  1. Source Code  
  2.   
  3. Problem: 1015  User: 3109034010   
  4. Memory: 268K  Time: 32MS   
  5. Language: C++  Result: Accepted   
  6.   
  7. Source Code   
  8. #include <iostream>   
  9. using namespace std;  
  10.   
  11. int d[201];             //存储所有人的d值   
  12. int p[201];             //存储所有人的p值   
  13. int result[21];         //存储最后选出的所有人的编号   
  14. int dp[21][801];        //dp[j][k]表示 选出j个人,辩控差为k时的最大辩控和   
  15.                         //辩控差的范围:【-400,400】,用下标表示【0,800】   
  16. int path[21][801];      //path[j][k]表示 第j个被选中的人的下标   
  17.   
  18. int cs = 0;             //记录测试用例的数目   
  19. int m, n;  
  20.   
  21.   
  22. //检测 已选择了a个人,辩控差为b且辩控和最大的方案 是否包含编号为i的人   
  23. bool CheckIsInPath( int a, int b, int i )  
  24. {  
  25.     while( (a>0) && path[a][b] != i )  
  26.     {  
  27.         b -= ( p[path[a][b]] - d[path[a][b]] );  
  28.         a--;  
  29.     }  
  30.       
  31.     return (a>0) ? true:false;  
  32. }  
  33.   
  34. //快排qsort的比较函数   
  35. int comp( const void *arg1, const void *arg2 )  
  36. {  
  37.     int result = *(int*)arg1 - *(int*)arg2;  
  38.     return result;  
  39. }  
  40.   
  41. //使用DP解决问题   
  42. void Solve()  
  43. {  
  44.     while( scanf( "%d%d", &n, &m ), n || m )                    //逗号表达式的值为最后一个表达式的值,即m,n都不为零   
  45.     {  
  46.         cs++;  
  47.   
  48.         int i;  
  49.         for( i=1; i<=n; i++ )  
  50.         {  
  51.             scanf( "%d%d", p+i, d+i );  
  52.         }  
  53.   
  54.         memset( dp, -1, sizeof( dp ) );                         //dp[j][k] == -1,表示该方案不存在,即j个人的辩控查不可能为k   
  55.         memset( path, 0, sizeof( path ) );  
  56.            
  57.         int original = m * 20;                                  //枚举辩控差,original对应实际辩控差为0,即【-m*20,m*20】---【0,m*20*2】   
  58.         dp[0][original] = 0;                                    //0个人,辩控差为0,最大辩控和为0   
  59.         forint j=0; j<m; j++ )                             //m个人,编号【0,m-1】   
  60.         {  
  61.             forint k=0; k<=2*original; k++ )                   //枚举辩控差,【0,m*20*2】   
  62.             {  
  63.                 if( dp[j][k] >= 0 )                              //如果方案可行   
  64.                 {  
  65.                     for( i=1; i<=n; i++ )                        //bottom to up,尝试增加一个人   
  66.                     {  
  67.                         if( dp[j+1][ k + p[i] - d[i] ] < dp[j][k] + p[i] + d[i] )//更新所有可能的辩控差   
  68.                         {  
  69.                             if( !CheckIsInPath( j, k, i ) )  
  70.                             {  
  71.                                 dp[j+1][ k + p[i] - d[i] ] = dp[j][k] + p[i] + d[i];  
  72.                                 path[j+1][ k + p[i] - d[i] ] = i;  
  73.                             }  
  74.                         }  
  75.                     }  
  76.                 }  
  77.             }  
  78.         }  
  79.           
  80.         for( i=0; dp[m][original+i]<0 && dp[m][original-i]<0; i++ );              //从原点开始向两边扫描   
  81.                                                                                     //可以保证辩控差绝对值最小         
  82.         int temp = ( dp[m][original+i] > dp[m][original-i] ) ? i : -i;               //绝对值相等时,取辩控和最大   
  83.         int sumP = ( dp[m][original+temp] + temp )/2;                               //(和-差)/ 2   
  84.         int sumD = ( dp[m][original+temp] - temp )/2;                               //(和+差)/ 2    
  85.         printf( "Jury #%d\n", cs );  
  86.         printf( "Best jury has value %d for prosecution and value %d for defence:\n", sumP, sumD );  
  87.   
  88.         memset( result, 0, sizeof(result) );  
  89.         int s = original+temp;  
  90.         for( i=1; i<=m; i++ )  
  91.         {             
  92.             result[i] = path[m+1-i][s];  
  93.             s -= p[result[i]] - d[result[i]];  
  94.   
  95.         }  
  96.         qsort( result+1,m,sizeof(int), comp );  
  97.         for( i=1; i<=m; i++ )  
  98.             printf( " %d", result[i] );  
  99.         printf( "\n\n" );  
  100.     }  
  101. }  
  102.   
  103. int main()  
  104. {  
  105.     Solve();  
  106.     return 0;  
  107. }  

参考文档:

http://blog.163.com/szuice_cream/blog/static/93813254200881171731604/

http://www.cppblog.com/mythit/archive/2010/10/14/88378.html?opt=admin

posted @ 2013-10-11 11:43  junxing  阅读(291)  评论(0)    收藏  举报