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

  • 相关阅读:
    Win10 安装GNU 编译器(gcc、g++ 和 gfortran)
    三维地图制作 数据选型 相关参考资料
    借助mapshaper的简化来修复geojson的拓扑错误
    一种改进后的turf.idw算法
    基于Geojson的点集的抽稀Js实现
    OL3-Cesium 二三维鼠标事件统一处理
    About TopoJSON
    基于 geojson数据类型面转线Transforms Polygons and MultiPolygons to LineStrings.
    数据库文档编写辅助脚本
    Extensible Messaging and Presence Protocol (XMPP): Core
  • 原文地址:https://www.cnblogs.com/wtyuan/p/12097094.html
Copyright © 2011-2022 走看看