• DFS(DP)---POJ 1014(Dividing)


    原题目http://poj.org/problem?id=1014

    题目大意

    有分别价值为1,2,3,4,5,66种物品,输入6个数字,表示相应价值的物品的数量,问一下能不能将物品分成两份,是两份的总价值相等,其中一个物品不能切开,只能分给其中的某一方,当输入六个0是(即没有物品了),这程序结束,总物品的总个数不超过20000

     


    输出:每个测试用例占三行:

               第一行: Collection #k: k为第几组测试用例

               第二行:是否能分(具体形式见用例)

               第三行:空白(必须注意,否则PE



    :采用第一种深搜方式,如果sum<halfvalue继续往下探索,如果大于则回溯到上一层,证明上一层选的不合适。认真分析就会发现问题,(1)这个树中的搜索路径很明显不支持同一个数字的若干次选取。(2)仅仅只有六层。这怎么可以呢!照此说不管怎么遍历都是1+2+3+4+5+6.

     

    这种搜索是合理的。每次探测都可以有六种选择,也可以想象成从六个盒子里面拿东西,每次从六个盒子里选一种,第一种出现的两种错误都可以避免掉。而且每个节点的度都是6,而且特征一样。这个非常好,对每个节点的处理都一样。很明显可以递归。符合DFS的特点。


    解题思路:

    有两种解决方法:

    第一种是几乎百度上所有同学都热衷的多重背包,确实这题就是《背包九讲》里面的“多重背包”的应用题,直接套O(V*Σlog n[i])的模板就毫无悬念地AC了,《背包九讲》里面提供的是“多重背包+二进制优化”算法,百度上也有不少同学加入了自己的想法去进一步优化,例如利用“抽屉原理”证明并“取模优化”的可行性等,这些同学都做了不少功课,值得我们学习。

     

        第二种方法是几乎没有同学使用的DFS,本题用DFS也能0ms跑完,可能大家都被《背包九讲》冲昏了头脑,都想着套模板去了,但又看不懂模板。呻吟“研究了背包多长时间都不完全明白”的同学不妨试试DFS。其实本来不少DP题都可以用搜索过的,大家不要钻牛角尖。

    解法一:DFS

    #include<iostream>
    using namespace std;
    
    int amount[7] = {0};
    int half_value = 0;
    int flag = 0;
    
    void DFS(int value, int pre){
    
    	if(value == half_value){
    		flag = 1;
    		return;
    	}
    
    	if(flag == 1){	// 到某一深度的时候接受到上层的(flag=1),那这层也要继续往上
    		return;
    	}
    
    	int i = 0;
    	for(i = pre; i > 0; i--){
    		if(amount[i]){
    			if(i + value <= half_value){
    				amount[i]--;
    				DFS(i + value, i);
    <span style="white-space:pre">						</span>//如果搜索到某一深度满足条件
    				if(flag == 1){	//回到上层
    					return;
    				}
    			}
    		}
    	}
    }
    
    
    int main(){
    
    	int testcase = 1;
    	while(true){
    		flag = 0;
    		int totalvalue = 0;
    		int N = 6;
    		int i = 1;
    		while(i <= N){
    			cin >> amount[i];
    			totalvalue += amount[i] * i;
    			i++;
    		}
    
    		if(!amount[1] && !amount[2] && !amount[3] && !amount[4] && !amount[5] && !amount[6]){
    			break;
    		}
    
    		printf("Collection #%d:
    ", testcase++);
    		if(totalvalue % 2 != 0){
    			cout << "Can't be divided." << endl << endl;
    			continue;
    		}
    
    		half_value = totalvalue / 2;
    		DFS(0, 6);		//注意由于6的价值比较大容易接近所要得的一半
    <span style="white-space:pre">					</span>//若取1可能会多走很多
    		if(flag){
    			cout << "Can be divided." << endl;
    		} else {
    			cout << "Can't be divided." << endl;
    		}
    		cout << endl;
    	}	
    	return 0;
    }


    (上面这个代码有个bug(题目还是可以AC的)( 0 0 3 0 3 1) 就过不了,上面是不能跳过中间值的,

    而数据是要6和3结合于是就呵呵了(求大牛赐教怎么修改)



    当然还有解法二:

    //Memory Time 
    //656K  16MS 
    
    /*多重背包+二进制优化*/
    
    #include<iostream>
    using namespace std;
    
    int n[7];  //价值为i的物品的个数
    int v;  //背包容量
    int SumValue;  //物品总价值
    bool flag;    //标记是否能平分SumValue
    int dp[100000];  //状态数组
    
    int max(int a,int b)
    {
    	return a>b?a:b;
    }
    
    /*完全背包*/
    void CompletePack(int cost,int weight)
    {
    	for(int i=cost;i<=v;i++)
    	{
    		dp[i]=max(dp[i],dp[i-cost]+weight);
    		if(dp[i]==v)    //剪枝,当能够平分SumValue时退出
    		{
    			flag=true;
    			return;
    		}
    	}
    			
    	return;
    }
    
    /*01背包*/
    void ZeroOnePack(int cost,int weight)
    {
    	for(int i=v;i>=cost;i--)
    	{
    		dp[i]=max(dp[i],dp[i-cost]+weight);
    		if(dp[i]==v)    //剪枝
    		{
    			flag=true;
    			return;
    		}
    	}
    	return;
    }
    
    /*多重背包*/
    void MultiplePack(int cost,int weight,int amount)
    {
    	if(cost*amount>=v)
    	{
    		CompletePack(cost,weight);
    		return;
    	}
    
    	if(flag)    //剪枝
    		return;
    
    	/*二进制优化*/
    	int k=1;
    	while(k<amount)
    	{
    		ZeroOnePack(k*cost,k*weight);
    
    		if(flag)    //剪枝
    			return;
    
    		amount-=k;
    		k*=2;
    	}
    	ZeroOnePack(amount*cost,amount*weight);
    
    	return;
    }
    
    int main(int i)
    {
    	int test=1;
    	while(cin>>n[1]>>n[2]>>n[3]>>n[4]>>n[5]>>n[6])
    	{
    		SumValue=0;  //物品总价值
    
    		for(i=1;i<=6;i++)
    			SumValue+=i*n[i];
    
    		if(SumValue==0)
    			break;
    
    		if(SumValue%2)    //sum为奇数,无法平分
    		{
    			cout<<"Collection #"<<test++<<':'<<endl;
    			cout<<"Can't be divided."<<endl<<endl;    //注意有空行
    			continue;
    		}
    
    		v=SumValue/2;
    		memset(dp,-1,sizeof(dp));
    		dp[0]=0;
    		flag=false;
    
    		for(i=1;i<=6;i++)
    		{
    			MultiplePack(i,i,n[i]);
    
    			if(flag)    //剪枝
    				break;
    		}
    
    		if(flag)
    		{
    			cout<<"Collection #"<<test++<<':'<<endl;
    			cout<<"Can be divided."<<endl<<endl;
    			continue;
    		}
    		else
    		{
    			cout<<"Collection #"<<test++<<':'<<endl;
    			cout<<"Can't be divided."<<endl<<endl;
    			continue;
    		}
    	}
    	return 0;
    }


    版权声明:本文为博主原创文章,未经博主允许不得转载。

    today lazy . tomorrow die .
  • 相关阅读:
    从零开始学android开发-setBackgroundDrawable与setBackgroundResource的区别
    从零开始学android开发-用Intent启动Activity的方法
    从零开始学android开发-View的setOnClickListener的添加方法
    从零开始学android开发- 应用程序窗体显示状态操作requestWindowFeature
    从零开始学android开发- layout属性介绍
    android常见错误--Unable to resolve target ‘android
    Html+jquery mobile
    用jQuery Mobile做HTML5移动应用的三个优缺点
    Chrome Apps将可以打包成iOS或Android应用
    [转]0.python:scikit-learn基本用法
  • 原文地址:https://www.cnblogs.com/france/p/4808761.html
Copyright © 2020-2023  润新知