LOJ6089 小 Y 的背包计数问题

题目

根号分类好题。

容易想到 \(f_{i,j}\) 表示拿到大小不超过 \(i\) 的物品,体积总和为 \(j\) 的方案数。

显然有 \(f_{i,j}=\sum_{k=0}^{\min(i,\frac ji)}f_{i-1,j-ik}\)

直接这样做是三方的。考虑优化。

\(g_{i,j}=\sum_{k=0}^{\frac ji}f_{i-1,j-ik}\)

那么 \(f_{i,j}=g_{i,j}-g_{i,j-i(i+1)}\)

这下就 \(O(n^2)\) 了。当然也可以用直接用容斥来理解。

然后可以很快发现,当 \(i^2>n\) 时,可以直接把物品 \(i\) 看成无限的。

考虑根号分类。对于不超过 \(\sqrt n\) 的物品,直接 DP,这部分是 \(O(n\sqrt n)\) 的。

对于别的物品的选法,考虑画出 Ferrers 图。例如 18=7+6+5 的 Ferrers 图长这样:

\(\texttt{0 0 0 0 0 0 0}\cdots 7\)

\(\texttt{0 0 0 0 0 0}\cdots 6\)

\(\texttt{0 0 0 0 0}\cdots 5\)

由于体积全部大于根号,所以每行的大小大于 \(\sqrt n\),每列的大小小于 \(\sqrt n\)

考虑把它对称过来,刚才那个变成这样:

\(\texttt{0 0 0}\)

\(\texttt{0 0 0}\)

\(\texttt{0 0 0}\)

\(\texttt{0 0 0}\)

\(\texttt{0 0 0}\)

\(\texttt{0 0}\)

\(\texttt{0}\)

它每列的大小大于 \(\sqrt n\),每行的大小小于 \(\sqrt n\)

由于对称是一一对应的关系,所以我们只要数对称过来的图有多少个。

问题转为『有 \(\sqrt n\) 种物品,第 \(i\) 种的体积为 \(i\),且要求最大的那种物品必须拿超过 \(\sqrt n\) 个,求方案数』。

做完全背包的过程中顺便算就好了。

#include<cstdio>
const int N=1e5+3,D=333,M=23333333;
int n,f[N],g[N],ans;
int main(){
	scanf("%d",&n);
	if(n<=D){
	f[0]=1;
	for(int i=1;i<=n;i++){
	  for(int j=i;j<=n;j++)
		f[j]=(f[j]+f[j-i])%M;
	  for(int j=n;j>=i*(i+1);j--)
		f[j]=(f[j]-f[j-i*(i+1)]+M)%M;
	}
	return 0*printf("%d\n",f[n]);
	}
	f[0]=1;
	for(int i=1;i<=D;i++){
	  for(int j=i;j<=n;j++)
		f[j]=(f[j]+f[j-i])%M;
	  for(int j=n;j>=i*(i+1);j--)
		f[j]=(f[j]-f[j-i*(i+1)]+M)%M;
	}
	g[0]=1;
	for(int i=1;i<D;i++)
	  for(int j=i;j<=n;j++){
		if(j+i*D<=n&&j>=i)ans=(ans+(long long)f[n-j-i*D]*g[j-i])%M;
		g[j]=(g[j]+g[j-i])%M;
	  }printf("%d\n",(ans+f[n])%M);
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/Camp-Nou/p/12601167.html
今日推荐