@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背包升级.看完题目以后很明显有背包的感觉,然后就往背包上靠 ...
随机推荐
- appium+python 启动一个app步骤
询问度娘搭好appium和python环境,开启移动app自动化的探索(基于Android),首先来记录下如何启动待测的app吧! 如何启动APP?1.获取包名:2.获取launcherActivit ...
- 常见任务&基本工具 1 软件包管理
打包系统主要有两个阵营 包文件的简介 Package files are created by a person known as a package maintainer, often (but n ...
- 依赖注入的方式(DI)
方式: 接口注入: setter方法注入: 构造方法注入: 接口注入: public class ClassA { private InterfaceB clzB; public void doSom ...
- pycharm最新激活码2017
最新的2017激活码 BIG3CLIK6F-eyJsaWNlbnNlSWQiOiJCSUczQ0xJSzZGIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYXNzaWduZW ...
- oracle 控制结构
1.if 逻辑结构 if/then 结构是最简单的条件测试,如果条件为真,则执行程序的一行或者多行,如果条件为假,则什么都不执行, 示例: if 1>2 then null; end if ; ...
- web前端学习(四)JavaScript学习笔记部分(3)-- JavaScript函数+异常处理+事件处理
1.Javascript函数-了解函数的用途 1.1.函数: 函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块 2.Javascript函数-定义函数 2.1.function必须小写 3. ...
- Can you find it? HDU-2141 (二分查找模版题)
Description Give you three sequences of numbers A, B, C, then we give you a number X. Now you need t ...
- 笔试面试记录-字符串转换成整型数等(aatoi,itoa)
C语言中经常用到字符串与数字之间的相互转换,常见的此类库函数有atof(字符串转换成浮点数).atoi(字符串转换成整型数).atol(字符串转换成长整形).itoa(整型数转换成字符串).ltoa( ...
- PYTHON网络爬虫与信息提取[正则表达式的使用](单元七)
正则表达式由字符和操作符构成 . 表示任何单个字符 []字符集,对单个字符给出取值范围 [abc]或者关系 [a-z]表示 [^abc]表示非这里面的东西 非字符集 * 表示星号之前的字符出现0次或 ...
- Leetcode914.X of a Kind in a Deck of Cards卡牌分组
给定一副牌,每张牌上都写着一个整数. 此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组: 每组都有 X 张牌. 组内所有的牌上都写着相同的整数. 仅当你可选的 X > ...