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

        感觉比较难的一题,即使做第三遍也感觉比较吃力。题目描述很清楚,英文理解无障碍。此题实质: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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值