[51nod1597]有限背包计数问题

  你有一个大小为n的背包,你有n种物品,第i种物品的大小为i,且有i个,求装满这个背包的方案数有多少
  两种方案不同当且仅当存在至少一个数i满足第i种物品使用的数量不同

 Input
  第一行一个正整数n
  1<=n<=10^5
 Output
  一个非负整数表示答案,你需要将答案对23333333取模

 
首先我们可以发现,令S=sqrt(n),那么对于大小大于S的物品,其实是用不完的,我们可以把他们的数量视为无限个
对于大小小于S的物品,我们可以令f[i][j]表示考虑了前i个物品,总大小为j的方案数,那么有:
f[i][j]=sum{f[i-1][j-k*i]},0<=k<=i
我们在DP的时候,假设当前要计算f[i][j],可以设tmp[v]为当前满足t mod i=v的f[i-1][t]的和
然后就可以通过维护tmp数组,轻松计算出f[i][j]了
这一步的时间复杂度是O(nsqrt(n))
 
接下来考虑大小大于S的物品
我们考虑一个给物品“动态添加大小”的DP:
令g[i][j]表示,当前有i个物品,大小总和为j
我们可以做的转移是:
(1):将所有物品的大小加一 :g[i][j]->g[i][j+i]
(2):新建一个大小为S+1的物品g[i][j]->g[i+1][j+S+1]
可以发现,物品总数最多为n/S个,所有g的第一维的规模是n/S的,所以这一个DP也是O(n*sqrt(n))的
于是总复杂度就是O(n*sqrt(n))
这道题还有更加优美的算法,可以用多项式黑科技进行推导,可以得到复杂度O(nlogn)的做法,由于出题人能力有限所以这里就不阐述了
 
 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<queue>
 6 #include<cmath>
 7 #define ll long long
 8 #define ui unsigned int
 9 #define ull unsigned long long
10 const int maxn=100233,modd=23333333;
11 int f[2][maxn],g[2][maxn],sm[maxn],tmp[maxn];
12 int i,j,k,n,m,n1;
13 
14 int ra;char rx;
15 inline int read(){
16     rx=getchar(),ra=0;
17     while(rx<'0'||rx>'9')rx=getchar();
18     while(rx>='0'&&rx<='9')ra*=10,ra+=rx-48,rx=getchar();return ra;
19 }
20 
21 #define MOD(x) x-=x>=modd?modd:0
22 #define UPD(x) x+=x<0?modd:0
23 int main(){
24     n=read(),n1=sqrt(n);register int i,j,k;
25     
26     bool now=1,pre=0;f[0][0]=1;int mod;
27     for(i=1;i<=n1;i++,std::swap(now,pre)){
28         memset(tmp,0,i<<2),mod=-1;
29         for(j=0,k=-i*i;j<=n;j++,k++){
30             if(++mod>=i)mod=0;
31             tmp[mod]+=f[pre][j],MOD(tmp[mod]),
32             f[now][j]=tmp[mod];
33             if(k>=0)tmp[mod]-=f[pre][k],UPD(tmp[mod]);
34         }
35     }//printf("n1:%d\n",n1);
36     //for(i=1;i<=n;i++)printf("  %d",f[pre][i]);puts("");
37     int n2=n/n1;
38     bool now1=1,pre1=0;g[0][0]=1;
39     for(i=1;i<=n2;i++,std::swap(now1,pre1)){
40         memset(g[now1],0,(n1+1)<<2);
41         for(j=n1+1,k=0;j<=n;j++,k++)g[now1][j]=g[pre1][k];
42         for(j=i;j<=n;j++)g[now1][j]+=g[now1][j-i],MOD(g[now1][j]);
43         for(j=1;j<=n;j++)sm[j]+=g[now1][j],MOD(sm[j]);
44     }
45     int ans=f[pre][n]+sm[n];MOD(ans);
46     for(i=1;i<n;i++)ans=(ans+1ll*f[pre][i]*sm[n-i])%modd;
47     printf("%d\n",ans);
48 }
View Code

 

posted @ 2016-10-10 19:24  czllgzmzl  阅读(281)  评论(0编辑  收藏  举报