@noi.ac - 508@ 01背包
@description@
有一天你学了一个能解决01背包问题的算法,你决定将这个算法应用到NOI比赛中。
你有一个大小为 V 的背包。
有 n 种物品,每一种物品均有 m 个。每一个物品都有一个体积,对于第 i 种物品中的第 j 个,它的体积为 vij。
你想把若干个物品按一定顺序放入背包,要求每一个物品只能使用一次且总体积不能超过 V,除此之外要求同种物品不能相邻。你想知道有多少种方案。
两种方案不同当且仅当二者选出的物品集合不同,或者物品集合相同但是这些物品的排列顺序不同。两个相同种类相同体积的物品算作不同物品。
你可以选择所有物品也可以一个物品都不选。
输出总方案数模 10^9+7 的值。
input
第一行三个整数 n,m,V,含义如题所述。
接下来 n 行,每行 m 个整数,其中第 i 行的第 j 个数为 vij 的值。
output
输出一行一个整数,表示总方案数模 10^9+7 的值。
sample input
2 2 3
1 2
2 2
sample output
9
explanation
我们把两个第一种物品用小写字母“a”和“b”表示,两个第二种物品用大写字母“A”和“B”表示。其中仅有a的体积为1,其他三个物品的体积均为2。
九种方案分别为:不选,a,b,A,B,aA,aB,Aa,和Ba。
注意AB不是一个合法的方案,因为它们的总体积为 2+2=4,大于3。
ab也不是一个合法的方案,因为虽然它们的总体积没有超过3,两个同种物品(i.e. a和b)在此方案中相邻。
对于100%的数据,1≤n,m≤50,1≤vij,V≤100。
@solution@
考虑假如已经给定了每种物品分别的数量,怎么求出使相同种类的物品不相邻的排列总数。
我们常见的使物品不相邻的组合计数方法有插空法,即先规划其他物品的位置再在其他物品的空隙中放入物品。
但因为要求每个种类的物品都互不相邻,故这种方法不能适用。因为有可能在当前相邻的物品,以后会加入某些物品将它们阻隔开。
考虑另一种方法:捆绑若干物品(即让这些物品必须在一起)然后容斥。
令 a[1...k] 表示一种捆绑方案,其中 a[i] 表示第 i 次捆绑将 a[i] 个物品捆绑成一组。可以通过搜索找到所有合法的 a。
通过手动推导可以得到如下的容斥式:
\]
其中 \(a[i]!\) 表示每个组内部的方案数(即内部进行全排列),\((-1)^{a[i]-1}\) 是容斥的系数,\(k!\) 即捆绑后的全排列。
但显然这个算法是不合格的。
我们依然沿用容斥+捆绑的思路,考虑改变思路方向,即不去枚举每种物品分别的数量。
可以发现最后的答案只跟捆绑后的组数以及每一组捆绑的物品个数有关。于是我们可以将若干物品捆绑后得到的组当作一个新物品,同时给予这个新物品权值(即上式中的 \((-1)^{a[i]-1}*(a[i]!)\))。
然后再将所有物品放在一起进行有权值的背包 dp,最后再乘一个阶乘表示全排列(即上式中的 \(k!\))。
然而这个算法有一点瑕疵:假如我们把物品 1, 2 合为一个物品,物品 2, 3 合为一个物品,不难发现这两个新物品是不能共存的。
但由于我们只会把同种类的物品合起来,所以我们可以再通过一个 dp 回避这个问题。
定义 dp[i][j][V] 表示 i 个物品分成 j 个组,占背包容量 V 的权值和。
假如新加入一个物品,要么它单独成组;要么它加入前面的某一个组,这个时候以前加入的每个物品后面都有一个位置,并且每个组的最前端也有一个位置,所以一共有 i+j 个位置可供选择(类似于第一类斯特林数,但第一类斯特林数是圆排列而这里是普通排列)。同时加入到前面的组里去后,组的大小改变,容斥的系数要乘 -1。
所以得到转移式:
\]
其中 v 是新加入物品的体积。
注意到题目中所有物品都是有体积的,故背包的容量就是背包可以装的物品数量的上界。
最后枚举背包被物品占据的体积 O(V),枚举背包内装有物品的数量 O(V),枚举物品种类 O(n),枚举这类物品占背包的容量 O(V),枚举这类物品有多少个组加入背包中 O(m)。
总时间复杂度 O(V^3nm)。可以调换枚举顺序先确定这类物品的信息,再判断这些信息方案数是否为 0,如果为 0 就跳过。
这个剪枝效果好到可以让你跑过这道题,在这么不科学的理论时间复杂度下。
@accepted code@
#include<cstdio>
const int MOD = int(1E9) + 7;
int f[50 + 5][50 + 5][100 + 5];
int dp[50 + 5][100 + 5][100 + 5], g[50 + 5][100 + 5];
int main() {
	int n, m, v, V; scanf("%d%d%d", &n, &m, &V);
	dp[0][0][0] = 1;
	for(int i=1;i<=n;i++) {
		for(int j=0;j<=m;j++)
			for(int k=0;k<=j;k++)
				for(int l=0;l<=V;l++)
					f[j][k][l] = 0;
		f[0][0][0] = 1;
		for(int j=1;j<=m;j++) {
			scanf("%d", &v);
			for(int p=j;p>=1;p--) {
				for(int q=p-1;q>=1;q--)
					for(int k=V;k>=v;k--)
						f[p][q][k] = (f[p][q][k] + (f[p-1][q-1][k-v] + 1LL*(MOD - 1)*(p + q - 1)%MOD*f[p-1][q][k-v]%MOD)%MOD)%MOD;
				for(int k=V;k>=v;k--)
					f[p][p][k] = (f[p][p][k] + f[p-1][p-1][k-v])%MOD;
			}
		}
		for(int j=0;j<=m;j++)
			for(int k=0;k<=V;k++) {
				g[j][k] = 0;
				for(int l=j;l<=m;l++)
					g[j][k] = (g[j][k] + f[l][j][k])%MOD;
			}
		for(int j=0;j<=m;j++)
			for(int k=0;k<=V;k++) {
				if( !g[j][k] ) continue;
				for(int p=j;p<=V;p++)
					for(int q=k;q<=V;q++)
						dp[i][p][q] = (dp[i][p][q] + 1LL*dp[i-1][p-j][q-k]*g[j][k])%MOD;
			}
	}
	int ans = 0;
	for(int i=0,f=1;i<=V;i++,f=1LL*f*i%MOD)
		for(int j=0;j<=V;j++)
			ans = (ans + 1LL*f*dp[n][i][j]%MOD)%MOD;
	printf("%d\n", ans);
}
@details@
算是这几天遇到的比较有意思的题。
再一次印证,容斥本身就是很难想的。。。
比如今年 THUWC 上的那道容斥题。。。
以及时间复杂度算出来明明需要跑 2.5*10^9 次却能在剪枝的情况下跑得飞快。。。
@noi.ac - 508@ 01背包的更多相关文章
- NOI 8785 装箱问题(0-1背包)
		
http://noi.openjudge.cn/ch0206/8785/ 描述 有一个箱子容量为V(正整数,0<=v<=20000),同时有n个物品(0< n<n<=30 ...
 - NYOJ-289 苹果 289  AC(01背包)                                                    分类:            NYOJ             2014-01-01 21:30    178人阅读    评论(0)    收藏
		
#include<stdio.h> #include<string.h> #define max(x,y) x>y?x:y struct apple { int c; i ...
 - 51nod1085(01背包)
		
题目链接: http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1085 题意: 中文题诶~ 思路: 01背包模板题. 用dp[ ...
 - hdu 2955 01背包
		
http://acm.hdu.edu.cn/showproblem.php?pid=2955 如果认为:1-P是背包的容量,n是物品的个数,sum是所有物品的总价值,条件就是装入背包的物品的体积和不能 ...
 - HDOJ 1203 I NEED A OFFER!(01背包)
		
10397507 2014-03-25 23:30:21 Accepted 1203 0MS 480K 428 B C++ 泽泽 题目链接:http://acm.hdu.edu.cn/showprob ...
 - [原]hdu2602 Bone Collector (01背包)
		
本文出自:http://blog.csdn.net/svitter 题意:典型到不能再典型的01背包.给了我一遍AC的快感. //=================================== ...
 - HDU 2126 Buy the souvenirs  (01背包,输出方案数)
		
题意:给出t组数据 每组数据给出n和m,n代表商品个数,m代表你所拥有的钱,然后给出n个商品的价值 问你所能买到的最大件数,和对应的方案数.思路: 如果将物品的价格看做容量,将它的件数1看做价值的话, ...
 - UVA 624 CD(01背包+输出方案)
		
01背包,由于要输出方案,所以还要在dp的同时,保存一下路径. #include <iostream> #include <stdio.h> #include <stri ...
 - POJ 2184 Cow Exhibition (01背包的变形)
		
本文转载,出处:http://www.cnblogs.com/Findxiaoxun/articles/3398075.html 很巧妙的01背包升级.看完题目以后很明显有背包的感觉,然后就往背包上靠 ...
 
随机推荐
- 学习Python笔记---if 语句
			
条件测试 每条if语句的核心都是一个值为True或False的表达式,这种表达式被称为条件测试.Python根据条件测试的值True还是False来决定是否执行if语句中的代码.如果条件测试的值为Tr ...
 - appium+python 启动一个app步骤
			
询问度娘搭好appium和python环境,开启移动app自动化的探索(基于Android),首先来记录下如何启动待测的app吧! 如何启动APP?1.获取包名:2.获取launcherActivit ...
 - 洛谷P1390 公约数的和  [2017年6月计划 数论12]
			
P1390 公约数的和 题目描述 有一天,TIBBAR和LXL比赛谁先算出1~N这N个数中每任意两个不同的数的最大公约数的和.LXL还在敲一个复杂而冗长的程序,争取能在100s内出解.而TIBBAR则 ...
 - Leetcode71. Simplify Path简化路径
			
给定一个文档 (Unix-style) 的完全路径,请进行路径简化. 例如, path = "/home/", => "/home" path = &qu ...
 - 怎么让一个不定宽高的div垂直水平居中?
			
方法一:使用CSS3 transform 父盒子设置:position:relative; div设置:position:absolute;transform:translate(-50%,-50%) ...
 - ML面试1000题系列(61-70)
			
本文总结ML面试常见的问题集 转载来源:https://blog.csdn.net/v_july_v/article/details/78121924 61.说说共轭梯度法? @wtq1993,htt ...
 - jquery源码学习(一)——jquery结构概述以及如何合适的暴露全局变量
			
jQuery 源码学习是对js的能力提升很有帮助的一个方法,废话不说,我们来开始学习啦 我们学习的源码是jquery-2.0.3已经不支持IE6,7,8了,因为可以少学很多hack和兼容的方法. jq ...
 - Vim 中自定义注释快捷键
			
写程序的时候写过的代码不忍心立马删掉,所以注释很多,所以找了下注释的快捷健. 打开 /etc/vim/vimrc文件,添加如下两行代码即可. /* 注释该行 */ map = I/* ^[A */j ...
 - LintCode刷题笔记-- Distinct Subsequences
			
标签:动态规划 题目描述: Given a string S and a string T, count the number of distinct subsequences of T in S. ...
 - Leetcode929.Unique Email Addresses独特的电子邮件地址
			
每封电子邮件都由一个本地名称和一个域名组成,以 @ 符号分隔. 例如,在 alice@leetcode.com中, alice 是本地名称,而 leetcode.com 是域名. 除了小写字母,这些电 ...