@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背包升级.看完题目以后很明显有背包的感觉,然后就往背包上靠 ...
随机推荐
- 删除 BIRT Report Viewer
去掉首页上的标题BIRT Report Viewer方法:找到Webroot\webcontent\birt\pages\layout\FramesetFragment.jsp文件,在里面定义了标题, ...
- Oracle启动和禁用约束及删除违反约束的记录
一.禁用约束 alter table table_name disable novalidate constraint constraint_name 二.批量导入数据 三.在开启约束之前一定要检查违 ...
- Laravel 使用 JWT 做 API 认证之tymon/jwt-auth 1.0.0-beta.1实践 - moell - SegmentFault
安装 将"tymon/jwt-auth": "1.0.0-beta.1" 添加到 composer.json 中,执行 composer update Prov ...
- 洛谷 2055 [ZJOI2009]假期的宿舍——二分图匹配
题目:https://www.luogu.org/problemnew/show/P2055 #include<iostream> #include<cstdio> #incl ...
- Linux下安装配置maven
参考博客: http://www.blogjava.net/caojianhua/archive/2011/04/02/347559.html 注意事项: 1.解压目录 我的maven解压目录为: / ...
- Codeforces Round #263 (Div. 2) A. Appleman and Easy Task【地图型搜索/判断一个点四周‘o’的个数的奇偶】
A. Appleman and Easy Task time limit per test 1 second memory limit per test 256 megabytes input sta ...
- CMake学习笔记二
CMake预定义变量 PROJECT_SOURCE_DIR 工程的根目录 PROJECT_BINARY_DIR 运行cmake命令的目录,通常是${PROJECT_SOURCE_DIR}/build ...
- 介绍elasticsearch的文件
elasticsearch.yml文件 打开上边的文件,我们看到下面的"集群"名称,节点名称 下图是文件的存储路径和日志路径 下面是监听的地址,默认是本机 下图指的是,集群是怎样搭 ...
- maven的配置和使用
Maven 简介 1.1 Maven 是什么 翻译为“专家”,“内行” Maven是跨平台的项目管理工具.主要服务于基于Java平台的项目构建,依赖管理和项目信息管理. 1.2 为什么使用Maven ...
- 关于 SSD 的接口和相关名词(2019-09-10)
关于 SSD 的接口和相关名词 了解了很多天的 SSD,太多的名词. 先记录一下. SATA MSATA M2 NVME NGFF U2 TODO: 后续收集相关信息.