LOJ #6089. 小 Y 的背包计数问题

LOJ #6089. 小 Y 的背包计数问题


神仙题啊orz。

首先把数分成\(<=\sqrt n\)的和\(>\sqrt n\)的两部分。

\(>\sqrt n\)的部分因为最多选\(\sqrt n\)个数,所以数量就没有卵用了。然后就用完全背包的一个常见套路(?)可以对一个空的序列整体+1或者在最左边加上一个\(\sqrt n+1\),这个操作序列和完全背包的选择方案一一对应。感性理解一下是对的emmmm,复杂度\(O(n\sqrt n)\)

\(<=\sqrt n\)的部分只有\(\sqrt n\)个数,就可以多重背包做,然后用剩余系优化。
剩余系就是说多重背包方案的转移方程是\(f[i][j]=\sum_{k=1}^{i}f[i-1][j-ki]\)这个样子的

可以发现转移过来的j都和原来的j同余\((\text{mod } i)\)

对于每一个\(\text{mod }i\)的余数做一个f的前缀和,转移过来的一定是连续的一段

orz

#include<bits/stdc++.h>
#define il inline
#define vd void
typedef long long ll;
il int gi(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
#define mod 23333333
ll f[320][100010];
ll h[100010],s[100010];
int main(){
	int n=gi(),m=int(sqrt(n));
	f[0][0]=1;s[0]=1;
	for(int i=1;i<=m;++i)
		for(int j=0;j<=n;++j){
			if(j>i)f[i][j]+=f[i][j-i];
			if(j>m)f[i][j]+=f[i-1][j-m-1];
			f[i][j]%=mod;
			s[j]=(s[j]+f[i][j])%mod;
		}
	memset(f,0,sizeof f);f[0][0]=1;
	for(int i=1;i<=m;++i){
		for(int j=0;j<i;++j){
			int t=0;
			for(int k=j;k<=n;k+=i)h[++t]=f[i-1][k];
			for(int k=2;k<=t;++k)h[k]=(h[k]+h[k-1])%mod;
			for(int k=j,tot=0;k<=n;k+=i){
				++tot;
				f[i][k]=(f[i][k]+h[tot]-h[std::max(0,tot-i-1)]+mod)%mod;
			}
		}
	}
	ll ans=0;
	for(int i=0;i<=n;++i)ans+=s[i]*f[m][n-i]%mod;
	printf("%lld\n",ans%mod);
	return 0;
}
posted @ 2018-09-19 15:44  菜狗xzz  阅读(177)  评论(0编辑  收藏  举报