[51nod1597]有限背包计数问题

[51nod1597]有限背包计数问题

试题描述

你有一个大小为n的背包,你有n种物品,第i种物品的大小为i,且有i个,求装满这个背包的方案数有多少
两种方案不同当且仅当存在至少一个数i满足第i种物品使用的数量不同

输入

第一行一个正整数n
1<=n<=10^5

输出

一个非负整数表示答案,你需要将答案对23333333取模

输入示例

3

输出示例

2

数据规模及约定

见“输入

题解

分块的形式真是多种多样,这题就是一个分块 dp。

这里分块的意思是对 1~i 这 n 种物品分类讨论。即对体积小于等于 sqrt(n) 的部分使用一种 dp 方法解决,对于体积大于 sqrt(n) 的部分使用另一种 dp 方法解决,最后由于“小于等于 sqrt(n) 和大于 sqrt(n) 的部分”没有交集,相互独立,可以使用乘法原理进行合并。

首先考虑只选用体积小于等于 sqrt(n) 的物品放入背包,这是一个多重背包问题,设 f(i, j) 表示考虑前 i 物品组成体积 j 的方案数,由于这题特殊性(体积为 i 的物品有 i 个),我们可以对 j mod i 将 f(i, j) 分类(共有 i 类),然后对于 j mod i = k 的类别计算一下 f(i, j) 前缀和 sum[j](共有 [n / i] 个前缀和),更新 f(i+1, j) 的时候就用 sum[j] - sum[j-i*i] 就好了(记得判断 j - i * i 会不会越界)

然后考虑体积大于 sqrt(n) 的物品,这个时候可以不考虑个数限制,因为每个物品不会选择超过 sqrt(n) 个。考虑另一种 dp,g(i, j) 表示选择了 i 个(注意是“个”不是“种”,显然 i ≤ sqrt(n))物品,组成体积 j 的方案数。这个 dp 中我们只关心体积最小的物品,有两种转移:一,放入一个新的体积最小的物品(体积为 sqrt(n) + 1);二,所有物品体积 +1。

注意:两种 dp 都需要开滚动数组。

最后用乘法原理乘起来,累加,这题就解决了。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cmath>
using namespace std;

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(x == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 100010
#define MOD 23333333
#define LL long long

int n, f[maxn], g[2][maxn], gsum[maxn];

int main() {
	n = read();
	int m = (int)sqrt(n + .5);
	
	f[0] = 1;
	for(int i = 1; i <= m; i++)
		for(int mod = 0; mod < i; mod++) {
			int sum = 0, j, cnt = 0;
			for(j = mod; j <= n; j += i) {
				sum += f[j];
				if(sum >= MOD) sum -= MOD;
				if(++cnt > i) {
					sum -= f[j-i*i];
					if(sum < 0) sum += MOD;
					cnt--;
				}
			}
			for(j -= i; j >= mod; j -= i) {
				sum -= f[j];
				if(sum < 0) sum += MOD;
				if(j >= i * i) {
					sum += f[j-i*i];
					if(sum >= MOD) sum -= MOD;
				}
				f[j] += sum;
				if(f[j] >= MOD) f[j] -= MOD;
			}
		}
//	for(int i = 0; i <= n; i++) printf("%d%c", f[i], i < n ? ' ' : '\n');
	
	int curg = 0;
	g[0][0] = 1;
	for(int i = 0; i <= m; i++, curg ^= 1) {
		memset(g[curg^1], 0, sizeof(g[curg^1]));
		for(int j = 0; j <= n; j++) if(g[curg][j]) {
//			printf("g %d %d: %d\n", i, j, g[curg][j]);
			gsum[j] += g[curg][j];
			if(gsum[j] >= MOD) gsum[j] -= MOD;
			if(i < m && j + m + 1 <= n) {
				g[curg^1][j+m+1] += g[curg][j];
				if(g[curg^1][j+m+1] >= MOD) g[curg^1][j+m+1] -= MOD;
			}
			if(i && j + i <= n) {
				g[curg][j+i] += g[curg][j];
				if(g[curg][j+i] >= MOD) g[curg][j+i] -= MOD;
			}
		}
	}
	curg ^= 1;
	
	int ans = 0;
	for(int i = 0; i <= n; i++) {
		ans += (LL)f[i] * gsum[n-i] % MOD;
		if(ans >= MOD) ans -= MOD;
	}
	printf("%d\n", ans);
	
	return 0;
}

 

posted @ 2017-09-02 10:51  xjr01  阅读(762)  评论(0编辑  收藏  举报