zoukankan      html  css  js  c++  java
  • POJ1837 01背包

    POJ1837

    题目

      大意是有一个“特殊”的天平,天平在不同位置分布着C((2le Cle 20))个挂钩,挂钩的位置坐标从-15到+15(-代表左臂,+代表右臂)。你有G((2le Gle 20))个砝码,砝码质量从1到25。问给定C个挂钩的位置坐标,G个砝码的质量,你有多少种悬挂方式使得天平平衡。

    Sample Input

    2 4	
    -2 3 
    3 4 5 8
    

    Sample Output

    2
    

      有2种悬挂方式如下:

    [egin{equation} (-2) * (3 + 4 + 5) + (3) * (8) = 0quad or quad (-2) * (4 + 8) + (3) * (3 + 5) = 0 end{equation} ]

    算法思路

      借鉴01背包的思想,本题中的砝码可以类比01背包中的物品,本题中将砝码挂到天平的挂钩上类比01背包中将物品放入背包中。01背包的状态定义:opt[i][j]表示前i件物品放入容量为j的背包中可以获得的最大价值,本题类似,opt[i][j]种,i可以表示前i个砝码,那么本题中j代表什么?这一点说不好想还真是不好想,如果动态规划比较熟可能比较容易想出来。仔细观察本题,题目要求有多少种悬挂方式使天平“平衡”,平衡即如式(1)所示,即所有砝码质量乘对应挂钩的位置坐标的和等于零,而我们可以让j表示这个“和”,那么opt[i][j]即表示前i个砝码悬挂到天平上,导致“和”为j可能的悬挂方式有多少种。进而可以推导出递推公式:

    [egin{equation} opt[i][j+hooks[k] * weights[i]] = opt[i][j+hooks[k] * weights[i]] + opt[i - 1][j] end{equation} ]

    (hooks[k])表示第k个挂钩的坐标,(weight[i])表示第i个砝码的质量,式(2)简单地说,当你把砝码i悬挂在挂钩k上时,“和”会由j变为(j+hooks[k] * weights[i]),则前i个砝码导致“和”为(j+hooks[k] * weights[i])的悬挂方式增多(opt[i - 1][j])种。注意标红的“增多”二字,表明可能还有其它悬挂方式达到(j+hooks[k] * weights[i])这个值,所以应该用(opt[i - 1][j])加上原先的值。而当你准备要放第i个砝码时,你可以悬挂在任意一个挂钩上,所以(1le kle C)

      另外,由题目的输入限制,可以确定j的范围为:(-15 imes20 imes25=-7500le jle 7500=15 imes20 imes25),j的范围包含负数,但是opt数组下标索引不允许出现负数,所以把j的范围向右平移7500,则范围变为(0le j le 15000)。题目初始化时,opt[0][7500]为1,表示未放砝码时,“和”为7500的悬挂方式有1种。最终输出结果为opt[g][7500],表示放了g个砝码,“和”仍为7500,即仍然保持未放砝码的状态,即平衡状态。

    代码

      若有0ms的实现方法,感谢评论告知。

    朴素实现

    Result: 1460kB, 47ms.

    #include <stdio.h>
    #include <iostream>
    
    int c, g;
    int hooks[20 + 5], weights[20 + 5];
    int opt[20 + 5][15000 + 5];//最大右偏20*25*15=7500,最大左偏-7500。由于数组下标不能索引负数,整体向右偏移7500,得0-15000
    int main() {
    	scanf("%d %d", &c, &g);
    	for (int i = 1; i <= c; i++)
    		scanf("%d", &hooks[i]);
    	for (int i = 1; i <= g; i++)
    		scanf("%d", &weights[i]);
    	opt[0][7500] = 1;//初始时,0件砝码是平衡的。
    	for (int i = 1; i <= g; i++)
    		for(int j = 0; j <= 15000; j++)//遍历所有可能出现的“和”
    			if(opt[i - 1][j])
    				for (int k = 1; k <= c; k++)
    					opt[i][j + hooks[k] * weights[i]] += opt[i - 1][j];
    	printf("%d
    ", opt[g][7500]);
    }
    

    优化一下j的循环次数

    Result: 936kB, 16ms

    #include <stdio.h>
    #include <iostream>
    #include <limits.h>
    
    int c, g;
    int hooks[20 + 5], weights[20 + 5];
    int opt[20 + 5][15000 + 5];//最大右偏20*25*15=7500,最大左偏-7500。由于数组下标不能索引负数,整体向右偏移7500,得0-15000
    int main() {
    	scanf("%d %d", &c, &g);
    	scanf("%d", &hooks[1]);
    	int min_hook = hooks[1], max_hook = min_hook;
    	for (int i = 2; i <= c; i++) {//找出最小的位置坐标和最大的位置坐标
    		scanf("%d", &hooks[i]);
    		if (hooks[i] < min_hook)
    			min_hook = hooks[i];
    		else if (hooks[i] > max_hook)
    			max_hook = hooks[i];
    	}
    	int sum_weight = 0;
    	for (int i = 1; i <= g; i++) {
    		scanf("%d", &weights[i]);
    		sum_weight += weights[i];//砝码质量和
    	}
    		
    	opt[0][7500] = 1;//初始时,0件砝码是平衡的。
    	for (int i = 1; i <= g; i++)
    		for (int j = 7500 + sum_weight * min_hook; j <= 7500 + sum_weight * max_hook; j++)//j的遍历范围从所有砝码放在min_hook到所有砝码放在max_hook
    			if (opt[i - 1][j])
    				for (int k = 1; k <= c; k++)
    					opt[i][j + hooks[k] * weights[i]] += opt[i - 1][j];
    	printf("%d
    ", opt[g][7500]);
    }
    

    优化一下空间复杂度

      从式(2)的状态转移方程可以看出,求opt[i]这一行的值只需要opt[i-1]这一行的值就可以了,而更之前的如opt[i-2]、opt[i-3]...这些行的值是不需要的。不过跟普通背包问题有点不一样,普通背包问题,有一个递增更改的关系(更改opt[i][j]只需要opt[i][k]的值就可以,其中k小于j),所以倒序遍历j,使得只需要一行即可(^{[1]}),而这个题不存在这种关系,所以需要两行。

    Result: 732kB, 16ms

      在朴素实现的基础上更改的,空间小了一半。时间上的变化不重要了,前后两次提交,一次是32ms,一次是16ms,空间两次都是固定732kB。

    #include <stdio.h>
    #include <string.h>
    #include <iostream>
    
    int c, g;
    int hooks[20 + 5], weights[20 + 5];
    int opt[2][15000 + 5];//最大右偏20*25*15=7500,最大左偏-7500。由于数组下标不能索引负数,整体向右偏移7500,得0-15000
    int main() {
    	scanf("%d %d", &c, &g);
    	for (int i = 1; i <= c; i++)
    		scanf("%d", &hooks[i]);
    	for (int i = 1; i <= g; i++)
    		scanf("%d", &weights[i]);
    	opt[0][7500] = 1;//初始时,0件砝码是平衡的。
    	for (int i = 1; i <= g; i++) {
    		int index = i % 2, index_minus_1 = index ^ 1;//i为奇数,对opt[1]进行更改,i为偶数,对opt[0]进行更改
    		memset(opt[index], 0, sizeof(opt[index]));
    		for (int j = 0; j <= 15000; j++) {
    			if (opt[index_minus_1][j])
    				for (int k = 1; k <= c; k++)
    					opt[index][j + hooks[k] * weights[i]] += opt[index_minus_1][j];
    		}
    	}
    	printf("%d
    ", opt[g % 2][7500]);//g为奇数,输出opt[1][7500],g为偶数,输出opt[0][7500]
    }
    

    参考:

    [1] 背包问题九讲 2.0

  • 相关阅读:
    POJ 3710 Christmas Game#经典图SG博弈
    POJ 2599 A funny game#树形SG(DFS实现)
    POJ 2425 A Chess Game#树形SG
    LeetCode Array Easy 122. Best Time to Buy and Sell Stock II
    LeetCode Array Easy121. Best Time to Buy and Sell Stock
    LeetCode Array Easy 119. Pascal's Triangle II
    LeetCode Array Easy 118. Pascal's Triangle
    LeetCode Array Easy 88. Merge Sorted Array
    ASP.NET MVC 学习笔记之 MVC + EF中的EO DTO ViewModel
    ASP.NET MVC 学习笔记之面向切面编程与过滤器
  • 原文地址:https://www.cnblogs.com/wtyuan/p/12097094.html
Copyright © 2011-2022 走看看