[题解] LOJ6039 小 Y 的背包计数

题面

题解

我 TM 以为是一道多项式题

考虑到这样一个性质

对于每个编号大于 \(\sqrt{n}\) 的物品, 我们不会选超过 \(\sqrt {n}\)

所以我们把整个选择分为两部分来看

第一部分是编号小于等于 \(\sqrt{n}\)

我们设 \(f[i][j]\) 代表选了编号前 \(i\) 个的, 体积总和为 \(j\) 的方案数

前缀和优化一下就行

第二部分是编号大于 \(\sqrt{n}\)

我们可以将选择变成这样两种操作

  • 把选择的 \(i\) 个数全部加一
  • 加入一个大小为 \(\sqrt {n} + 1\) 的数

可以证明这两个操作可以把编号大于 \(\sqrt{n} + 1\) 的选择方案全部不重不漏的表达出来

没了, 两个乘起来就行了

Code

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
const int mod = 23333333;
const int N = 1e5 + 5; 
using namespace std;

int n, m, f[405][N], g[405][N], s[N], ans; 

template < typename T >
inline T read()
{
	T x = 0, w = 1; char c = getchar();
	while(c < '0' || c > '9') { if(c == '-') w = -1; c = getchar(); }
	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * w; 
}

int main()
{
	n = read <int> (), m = sqrt(n);
	for(int i = 0; i <= m; i++)
		f[i][0] = 1; 
	for(int i = 1; i <= m; i++)
	{
		for(int j = 0; j <= n; j++)
			s[j] = ((j - i >= 0 ? s[j - i] : 0) + f[i - 1][j]) % mod; 
		for(int j = 0; j <= n; j++)
			f[i][j] = (s[j] - (j - i * i - i >= 0 ? s[j - i * i - i] : 0) + mod) % mod; 
	}
	memset(s, 0, sizeof(s)), g[0][0] = 1; 
	for(int i = 1; i <= m; i++)
		for(int j = 0; j <= n; j++)
		{
			if(j - i > 0) g[i][j] = g[i][j - i]; 
			if(j - m > 0) g[i][j] = (g[i][j] + g[i - 1][j - m - 1]) % mod; 
			s[j] = (s[j] + g[i][j]) % mod; 
		}
	for(int i = 0; i <= n; i++)
		ans = (1ll * ans + 1ll * s[n - i] * f[m][i] % mod) % mod;
	ans = (1ll * ans + f[m][n]) % mod; 
	printf("%d\n", ans); 
	return 0; 
}

posted @ 2020-01-12 12:56  ztlztl  阅读(110)  评论(0编辑  收藏  举报