LOJ #6089. 小 Y 的背包计数问题
思路
一道思维量很高的 DP 计数。
注意到是恰好为 \(n\)。由于数据量很大,因此暴力会 T 飞。
发现这个物品的个数限制很有规律,考虑利用一下。
发现对于体积大于 \(\sqrt n\) 的物品,我们不可能将其用完,也就是说我们可以将这个问题分成两部分:体积小于等于 \(\sqrt n\) 的多重背包,体积大于 \(\sqrt n\) 的完全背包。最后将其合并也就是体积互补的部分对应相乘即可。
对于多重背包的部分,做法是显然的。直接计数即可。具体的,设 \(f_{i,j}\) 表示考虑到前 \(i\)
个体积为 \(i\) 的物品,组成的大小为 \(j\) 的方案数。有转移:
注意到这个东西可以用前缀和优化。具体的可以看一下代码。
对于完全背包的部分,我们需要开大脑洞使用一些技巧。我们通过如下方式来不重不漏的统计方案:
我们有两种操作:
- 将当前背包里的所有物品的体积增加 1。
 - 插入一个新的大小为 \(\left \lfloor \sqrt n \right \rfloor +1\) 的物品。
 
容易发现这样可以遍历完所有的情况同时不重不漏。(这里我当时理解了相对久。实际上这种思路可能非常难以自己想出来。如果看不懂可以直接看转移方程)
因此我们设 \(g_{i,j}\) 表示当前背包中装有 \(i\) 个物品,大小和为 \(j\) 的方案数,根据上述方式遍历情况有如下转移:
注意到是完全背包,因此这个整体加 1 的操作可以进行任意次,因此 \(j\) 需要从小到大枚举。
我们设 \(G_j\) 表示完全背包的部分大小为 \(j\) 的总情况数。因为我们并不管背包中有多少个数,因此有
根据定义我们答案即为
code
根据写法的不同,可能最后统计答案的时候需要注意一下。由于个人的写法,\(G_0\) 会是 0,但实际上应该是 1(全都不选)。因此我统计答案的时候需要单独加上一个 \(f_{\left \lfloor \sqrt n \right \rfloor,n}\)。
注意取模。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N=1e5+7,M=355,p=23333333;
int n,f[M][N],tmp[N],g[M][N],G[N];
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;int c=sqrt(n);f[0][0]=1;
	for(int i=1;i<=c;i++){
		for(int j=0;j<i;j++)tmp[j]=f[i-1][j];
		for(int j=i;j<=n;j++)tmp[j]=(f[i-1][j]+tmp[j-i])%p;
		for(int j=0;j<=n;j++){
			if(j>=i*(i+1))f[i][j]=(tmp[j]-tmp[j-(i+1)*i]+p)%p;
			else f[i][j]=tmp[j];
		}
	}
	g[0][0]=1;
	for(int i=1;i<=c;i++){
		for(int j=i*(c+1);j<=n;j++)g[i][j]=(g[i-1][j-(c+1)]+g[i][j-i])%p;
	}
	for(int i=0;i<=n;i++)for(int j=1;j<=c;j++)G[i]=(G[i]+g[j][i])%p;
	int ans=f[c][n];//注意到第二组全都不选也是一种方案 
	for(int i=0;i<=n;i++)ans=(ans+1ll*f[c][i]*G[n-i]%p)%p;
	cout<<ans;return 0;
}
                    
                
                
            
        
浙公网安备 33010602011771号