poj1015动态规划
感觉比较难的一题,即使做第三遍也感觉比较吃力。题目描述很清楚,英文理解无障碍。此题实质:n个物体中选m个。
最容易想到的当然是搜索,即DFS。从1到n依次进行扫描,判断是否被选中。每选够m个,就与前面的结果进行一次比较。因为Max( n ) = 200,Max( m ) = 20所以搜索次数大致有 ,这样的规模应该比较恐怖。因此,如果不优化,暴搜必挂无疑。如何剪枝?也就是哪些状态不用搜索,可以直接跳过。例如,我们要在20个人中选10个人。在以前的搜索中,已经取得了最优解S。当前已经选择了5个人,如果按照目前的情况无论如何也不可能取到比S更优的解,这时就可以直接放弃,没有必要继续搜索剩下的5人。当然,按照这种思路可以剪枝。但是,算法本省已确定的了它的复杂度是指数级,剪枝能修改的只是系数。因此,意义不大。
- #include <iostream>
- #include <cmath>
- using namespace std;
- //***********************常量定义*****************************
- const int NUM = 205;
- const int INF_MIN = -999999999;
- const int INF_MAX = 999999999;
- //*********************自定义数据结构*************************
- //********************题目描述中的变量************************
- int n, m;
- int dValue[NUM];
- int pValue[NUM];
- //**********************算法中的变量**************************
- bool tmpUsed[NUM];
- bool used[NUM];
- int sub = INF_MAX;
- int sum = INF_MIN;
- //***********************算法实现*****************************
- void DFS( int curNum, int curId, int sumD, int sumP )
- {
- int x = abs(sumD-sumP);
- int y = sumD+sumP;
- if( curNum == m )
- {
- if( x < sub || ( x == sub && y > sum ) )
- {
- memcpy( used, tmpUsed, sizeof(used) );
- sub = x;
- sum = y;
- }
- return;
- }
- //此处可剪枝
- //if( ??? ) return;
- int i;
- for( i=curId; i<n; i++ )
- {
- if( !tmpUsed[i] )
- {
- tmpUsed[i] = true;
- DFS( curNum+1, curId+1, sumD+dValue[i], sumP+pValue[i] );
- tmpUsed[i] = false;
- }
- }
- }
- //************************main函数****************************
- int main()
- {
- freopen( "in.txt", "r", stdin );
- int caseNum = 0;
- while( cin >> n >> m, !( n == 0 && m == 0 ) )
- {
- for( int i=0; i<n; i++ )
- {
- cin >> pValue[i] >> dValue[i];
- }
- sub = INF_MAX;
- sum = INF_MIN;
- memset( tmpUsed, false, sizeof(tmpUsed) );
- memset( used, false, sizeof(used) );
- DFS( 0, 0, 0, 0 );
- int ansD = 0;
- int ansP = 0;
- for( int j=0; j<n; j++ )
- {
- if( used[j] )
- {
- ansD += dValue[j];
- ansP += pValue[j];
- }
- }
- cout << "Jury #" << ++caseNum << endl;
- cout << "Best jury has value " << ansP << " for prosecution and value " << ansD << " for defence:" << endl;
- for( int k=0; k<n; k++ )
- {
- if( used[k] ) cout << " " << k+1;
- }
- cout << endl;
- }
- return 0;
- }
从动态规划出发,继续考虑。现在要在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的状态是否已计算过。
- Source Code
- Problem: 1015 User: 3109034010
- Memory: 31696K Time: 266MS
- Language: C++ Result: Accepted
- Source Code
- #include <stdio.h>
- #include <memory.h>
- int Sum_d[25],Sum_p[25],jury_d[205],jury_p[205];
- int Best_d[25][205][805],Best_p[25][205][805];
- char flag[25][205][805],choose[25][205][805];
- int ans_jury[205];
- int abs(int num)//返回num的绝对值
- {
- if(num<0)
- return -num;
- else
- return num;
- }
- int DP(int n,int m,int& dp_d,int& dp_p)
- {
- int yes_d,yes_p,no_d,no_p,tm_d=dp_d,tm_p=dp_p;
- int tm1,tm2;
- if(m==0)
- {
- return 0;
- }//已经选够人
- if(m==n)
- {
- dp_d+=Sum_d[m];
- dp_p+=Sum_p[m];
- return 0;
- }//在a个人里选a个。。
- if(flag[m][n][400+tm_d-tm_p])
- {
- dp_d+=Best_d[m][n][400+tm_d-tm_p];
- dp_p+=Best_p[m][n][400+tm_d-tm_p];
- return 0;
- }//已经算过的状态。
- flag[m][n][400+dp_d-dp_p]=1;
- no_d=dp_d;
- no_p=dp_p;
- DP(n-1,m,no_d,no_p);//选择n
- yes_d=dp_d+jury_d[n];
- yes_p=dp_p+jury_p[n];
- DP(n-1,m-1,yes_d,yes_p);//抛弃n
- tm1=abs(yes_d-yes_p)-abs(no_d-no_p);
- tm2=(yes_d+yes_p)-(no_d+no_p);
- if((tm1<0)||((tm1==0)&&(tm2>0)))
- {
- dp_d=yes_d;
- dp_p=yes_p;
- choose[m][n][400+tm_d-tm_p]=1;
- }
- else
- {
- dp_d=no_d;
- dp_p=no_p;
- choose[m][n][400+tm_d-tm_p]=0;
- }//取最优解
- Best_d[m][n][400+tm_d-tm_p]=dp_d-tm_d;
- Best_p[m][n][400+tm_d-tm_p]=dp_p-tm_p;//记录状态
- return 0;
- }
- int doSearch(int m,int n,int pos)//搜索培训团组合。
- {
- int i;
- if(m==0)
- return 0;
- if(m==n)
- {
- for(i=1;i<=n;i++)
- ans_jury[i]++;
- return 0;
- }
- if(choose[m][n][400+pos]==1)
- {
- ans_jury[n]++;
- doSearch(m-1,n-1,pos+jury_d[n]-jury_p[n]);
- }
- else
- {
- doSearch(m,n-1,pos);
- }
- return 0;
- }
- int main()
- {
- int i,m,n,t=0;
- int ans_d=0,ans_p=0;
- while(scanf("%d%d",&n,&m),m+n>0)
- {
- t++;
- Sum_d[0]=0;
- Sum_p[0]=0;
- ans_d=0;
- ans_p=0;
- memset(flag,0,sizeof(flag));
- memset(choose,0,sizeof(choose));
- memset(ans_jury,0,sizeof(ans_jury));
- for(i=1;i<=n;i++)
- {
- scanf("%d%d",&jury_d[i],&jury_p[i]);
- if(i<=m)
- {
- Sum_d[i]=Sum_d[i-1]+jury_d[i];
- Sum_p[i]=Sum_p[i-1]+jury_p[i];
- }
- }
- DP(n,m,ans_d,ans_p);
- doSearch(m,n,0);
- printf("Jury #%d\nBest jury has value %d for prosecution and value %d for defence:\n",t,ans_d,ans_p);
- for(i=1;i<=n;i++)
- {
- if(ans_jury[i])
- printf(" %d",i);
- }
- printf("\n\n");
- }
- return 0;
- }
网上还有另外一种解法,枚举D(j) – P(j)。要选m个人,使|D(j) – P(j)|最小。如果已选了m-1个人,再选1个人即可。选的这一个人,只要使m个人的|D(j) – P(j)|最小即可。
- Source Code
- Problem: 1015 User: 3109034010
- Memory: 268K Time: 32MS
- Language: C++ Result: Accepted
- Source Code
- #include <iostream>
- using namespace std;
- int d[201]; //存储所有人的d值
- int p[201]; //存储所有人的p值
- int result[21]; //存储最后选出的所有人的编号
- int dp[21][801]; //dp[j][k]表示 选出j个人,辩控差为k时的最大辩控和
- //辩控差的范围:【-400,400】,用下标表示【0,800】
- int path[21][801]; //path[j][k]表示 第j个被选中的人的下标
- int cs = 0; //记录测试用例的数目
- int m, n;
- //检测 已选择了a个人,辩控差为b且辩控和最大的方案 是否包含编号为i的人
- bool CheckIsInPath( int a, int b, int i )
- {
- while( (a>0) && path[a][b] != i )
- {
- b -= ( p[path[a][b]] - d[path[a][b]] );
- a--;
- }
- return (a>0) ? true:false;
- }
- //快排qsort的比较函数
- int comp( const void *arg1, const void *arg2 )
- {
- int result = *(int*)arg1 - *(int*)arg2;
- return result;
- }
- //使用DP解决问题
- void Solve()
- {
- while( scanf( "%d%d", &n, &m ), n || m ) //逗号表达式的值为最后一个表达式的值,即m,n都不为零
- {
- cs++;
- int i;
- for( i=1; i<=n; i++ )
- {
- scanf( "%d%d", p+i, d+i );
- }
- memset( dp, -1, sizeof( dp ) ); //dp[j][k] == -1,表示该方案不存在,即j个人的辩控查不可能为k
- memset( path, 0, sizeof( path ) );
- int original = m * 20; //枚举辩控差,original对应实际辩控差为0,即【-m*20,m*20】---【0,m*20*2】
- dp[0][original] = 0; //0个人,辩控差为0,最大辩控和为0
- for( int j=0; j<m; j++ ) //m个人,编号【0,m-1】
- {
- for( int k=0; k<=2*original; k++ ) //枚举辩控差,【0,m*20*2】
- {
- if( dp[j][k] >= 0 ) //如果方案可行
- {
- for( i=1; i<=n; i++ ) //bottom to up,尝试增加一个人
- {
- if( dp[j+1][ k + p[i] - d[i] ] < dp[j][k] + p[i] + d[i] )//更新所有可能的辩控差
- {
- if( !CheckIsInPath( j, k, i ) )
- {
- dp[j+1][ k + p[i] - d[i] ] = dp[j][k] + p[i] + d[i];
- path[j+1][ k + p[i] - d[i] ] = i;
- }
- }
- }
- }
- }
- }
- for( i=0; dp[m][original+i]<0 && dp[m][original-i]<0; i++ ); //从原点开始向两边扫描
- //可以保证辩控差绝对值最小
- int temp = ( dp[m][original+i] > dp[m][original-i] ) ? i : -i; //绝对值相等时,取辩控和最大
- int sumP = ( dp[m][original+temp] + temp )/2; //(和-差)/ 2
- int sumD = ( dp[m][original+temp] - temp )/2; //(和+差)/ 2
- printf( "Jury #%d\n", cs );
- printf( "Best jury has value %d for prosecution and value %d for defence:\n", sumP, sumD );
- memset( result, 0, sizeof(result) );
- int s = original+temp;
- for( i=1; i<=m; i++ )
- {
- result[i] = path[m+1-i][s];
- s -= p[result[i]] - d[result[i]];
- }
- qsort( result+1,m,sizeof(int), comp );
- for( i=1; i<=m; i++ )
- printf( " %d", result[i] );
- printf( "\n\n" );
- }
- }
- int main()
- {
- Solve();
- return 0;
- }
参考文档:
http://blog.163.com/szuice_cream/blog/static/93813254200881171731604/
http://www.cppblog.com/mythit/archive/2010/10/14/88378.html?opt=admin

浙公网安备 33010602011771号