zoukankan      html  css  js  c++  java
  • [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 ? ' ' : '
    ');
    	
    	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
    ", 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
    ", ans);
    	
    	return 0;
    }
    
  • 相关阅读:
    img标签中的alt属性在IE6/7/8中的兼容问题
    fontsize可以解决img标签插入图片之间的缝隙
    BFC
    为什么 input 元素能用 width 属性
    <textarea>使用的时候发现的两个问题的总结
    c语言-概述
    C语言- while 语句
    C语言- for 语句
    C语言- if 语句
    C语言-编译运行程序
  • 原文地址:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/7466009.html
Copyright © 2011-2022 走看看