消失之物「分治+背包」
消失之物「分治+背包」
题目描述
ftiasch 有 \(N\) 个物品, 体积分别是 \(W_1, W_2, ..., W_N\)。 由于她的疏忽, 第 \(i\) 个物品丢失了。 “要使用剩下的 \(N - 1\) 物品装满容积为 \(x\) 的背包,有几种方法呢?” -- 这是经典的问题了。她把答案记为 \(Count(i, x)\) ,想要得到所有 \(1 <= i <= N, 1 <= x <= M\) 的 \(Count(i, x)\) 表格。
输入格式
第 \(1\) 行:两个整数 \(N (1 ≤ N ≤ 2 × 10^3)\) 和 \(M (1 ≤ M ≤ 2 × 10^3)\),物品的数量和最大的容积。
第 \(2\) 行: \(N\) 个整数 \(W_1, W_2, ..., W_N\), 物品的体积。
输出格式
一个 \(N × M\) 的矩阵, \(Count(i, x)\) 的末位数字
样例
样例输入
3 2
1 1 2
样例输出
11
11
21
思路分析
- 直接暴力跑一个背包能拿70~80分(
甚至完全可以用背包水过) - 不难发现,在每一次更新不能拿的物品时,其它可以拿的是有大量的重复计算的,所以考虑将其保留,直接使用
- 这时候就可以用分治了,每次把一个区间分成两部分,一部分不动,另一部分去选不能拿的
- 另外要注意,每一次更新答案之前要撤销对另一半答案的更新
- 最后会分成一个长得像线段树的东西,每个相当于叶子结点的就代表该点不能拿,一共有 \(log(n)\) 层
详见代码
\(Code\)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 20200
#define R register
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,a[N],f[20][N];
void solve(int l,int r,int dep){ //dep表示当前分治树的深度
if(l==r){
for(R int i = 1;i <= m;i++)printf("%d",f[dep][i]);
puts("");
return;
}
int mid = (l+r)>>1;
for(R int i = m;i >= 0;i--)f[dep+1][i] = f[dep][i];
for(R int i = mid+1;i <= r;i++){ //右半部分全部都能拿,直接更新即可
for(R int j = m;j >= 0;j--){
f[dep+1][j] = (f[dep+1][j]+f[dep+1][j-a[i]])%10;
}
}
solve(l,mid,dep+1); //左半部分有不能拿的,递归解决
for(R int i = m;i >= 0;i--)f[dep+1][i] = f[dep][i]; //反过来再处理一下,在此之前要撤销
for(R int i = l;i <= mid;i++){
for(R int j = m;j >= 0;j--){
f[dep+1][j] = (f[dep+1][j]+f[dep+1][j-a[i]])%10;
}
}
solve(mid+1,r,dep+1);//同上
}
int main(){
n = read(),m = read();
for(R int i = 1;i <= n;i++)a[i] = read();
f[0][0] = 1;
solve(1,n,0);
return 0;
}