bzoj2287 [POJ Challenge]消失之物
这么一道水题我还用各种麻烦的方法去做……这人没救了
看在这题做法挺多的份上,我就都写写好了……
1.CDQ分治
这个做法是我想到的……因为受到了Eden的新背包问题的启发……
定义solve(l,r)表示删除编号在[l,r]的物品并计算其DP数组,显然这个是可以折半往下递归的,用没删掉的那一半更新一下DP数组并带着DP数组递归下去即可。
T(n)=2T(n/2)+O(nm),解得T(n)=O(nmlogn),并且比O(nm)的做法慢不了多少。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=2010; 6 void CDQ(int,int,int); 7 int n,m,w[maxn],f[15][maxn]={{0}}; 8 int main(){ 9 scanf("%d%d",&n,&m); 10 for(int i=1;i<=n;i++)scanf("%d",&w[i]); 11 f[0][0]=1; 12 CDQ(1,n,0); 13 return 0; 14 } 15 void CDQ(int l,int r,int k){ 16 if(l==r){ 17 for(int i=1;i<=m;i++)printf("%d",f[k][i]); 18 printf("\n"); 19 return; 20 } 21 int mid=(l+r)>>1; 22 copy(f[k],f[k]+m+1,f[k+1]); 23 for(int i=mid+1;i<=r;i++)for(int j=m;j>=w[i];j--){ 24 f[k+1][j]+=f[k+1][j-w[i]]; 25 if(f[k+1][j]>=10)f[k+1][j]%=10; 26 } 27 CDQ(l,mid,k+1); 28 copy(f[k],f[k]+m+1,f[k+1]); 29 for(int i=l;i<=mid;i++)for(int j=m;j>=w[i];j--){ 30 f[k+1][j]+=f[k+1][j-w[i]]; 31 if(f[k+1][j]>=10)f[k+1][j]%=10; 32 } 33 CDQ(mid+1,r,k+1); 34 }
2.FFT
好吧表示这个做法是lrd一开始想的……然而他并不会FFT
定义f[i][j]表示用前i个物品凑出j的方案数,g[i][j]表示用后i个物品凑出j的方案数,h[i][j]表示去掉i后凑出j的方案数,显然有
\begin{align}h[i][j]=\sum_{k=0}^j{f[i-1][k]*g[i+1][j-k]}\end{align}
可以发现这就是个裸卷积,FFT加速即可,复杂度显然是O(nmlogn),然而常数大如狗,跑的比CDQ分治慢到不知哪里去了……
1 #include<cstdio> 2 #include<cstring> 3 #include<cmath> 4 #include<algorithm> 5 using namespace std; 6 const int maxn=2010; 7 const double pi=acos(-1.0),eps=5e-3; 8 struct Complex{ 9 double a,b; 10 Complex(double a=0.0,double b=0.0):a(a),b(b){} 11 Complex operator+(const Complex &x)const{return Complex(a+x.a,b+x.b);} 12 Complex operator-(const Complex &x)const{return Complex(a-x.a,b-x.b);} 13 Complex operator*(const Complex &x)const{return Complex(a*x.a-b*x.b,a*x.b+b*x.a);} 14 Complex &operator*=(const Complex &x){return *this=*this*x;} 15 }A[maxn<<2],B[maxn<<2]; 16 void FFT(Complex*,int,int); 17 int n,m,N=1,w[maxn],f[maxn][maxn],g[maxn][maxn]; 18 int main(){ 19 scanf("%d%d",&n,&m); 20 while(N<=m)N<<=1;N<<=1; 21 f[0][0]=1; 22 for(int i=1;i<=n;i++){ 23 scanf("%d",&w[i]); 24 copy(f[i-1],f[i-1]+m+1,f[i]); 25 for(int j=m;j>=w[i];j--){ 26 f[i][j]+=f[i-1][j-w[i]]; 27 if(f[i][j]>=10)f[i][j]%=10; 28 } 29 } 30 g[n+1][0]=1; 31 for(int i=n;i;i--){ 32 copy(g[i+1],g[i+1]+m+1,g[i]); 33 for(int j=m;j>=w[i];j--){ 34 g[i][j]+=g[i+1][j-w[i]]; 35 if(g[i][j]>=10)g[i][j]%=10; 36 } 37 } 38 for(int i=1;i<=n;i++){ 39 fill(A,A+N,Complex(0.0,0.0)); 40 for(int j=0;j<=m;j++)A[j].a=f[i-1][j]; 41 fill(B,B+N,Complex(0.0,0.0)); 42 for(int j=0;j<=m;j++)B[j].a=g[i+1][j]; 43 FFT(A,N,1); 44 FFT(B,N,1); 45 for(int j=0;j<N;j++)A[j]*=B[j]; 46 FFT(A,N,-1); 47 for(int j=1;j<=m;j++)printf("%d",(int)(A[j].a+eps)%10); 48 printf("\n"); 49 } 50 return 0; 51 } 52 void FFT(Complex *A,int n,int tp){ 53 for(int i=1,j=0,k;i<n-1;i++){ 54 k=n; 55 do j^=(k>>=1);while(j<k); 56 if(i<j)swap(A[i],A[j]); 57 } 58 for(int k=2;k<=n;k<<=1){ 59 Complex wn(cos(-tp*2*pi/k),sin(-tp*2*pi/k)); 60 for(int i=0;i<n;i+=k){ 61 Complex w(1.0,0.0); 62 for(int j=0;j<(k>>1);j++,w*=wn){ 63 Complex a=A[i+j],b=w*A[i+j+(k>>1)]; 64 A[i+j]=a+b; 65 A[i+j+(k>>1)]=a-b; 66 } 67 } 68 } 69 if(tp<0)for(int i=0;i<n;i++)A[i].a/=n; 70 }
3.DP(想不到更好的称呼了……)
这个做法是lrz告诉我的……我怎么这么愚蠢连这都想不到
首先把用所有物品凑出j的方案数全都求出来,因为填满j的方案数不受物品顺序影响,而把第i个物品放到最后之后是可以把DP方程逆变换回去得到删掉i之后凑出j的方案数,然后就果断O(nm)了……咦怎么才比多一个log的CDQ分治快这么一点
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=2010; 6 int n,m,w[maxn],f[maxn]={0},g[maxn]; 7 int main(){ 8 scanf("%d%d",&n,&m); 9 f[0]=1; 10 for(int i=1;i<=n;i++){ 11 scanf("%d",&w[i]); 12 for(int j=m;j>=w[i];j--){ 13 f[j]+=f[j-w[i]]; 14 if(f[j]>=10)f[j]%=10; 15 } 16 } 17 for(int i=1;i<=n;i++){ 18 copy(f,f+m+1,g); 19 for(int j=w[i];j<=m;j++){ 20 g[j]-=g[j-w[i]]; 21 if(g[j]<0)g[j]+=10; 22 } 23 for(int j=1;j<=m;j++)printf("%d",g[j]); 24 printf("\n"); 25 } 26 return 0; 27 }
这个故事告诉我们一个道理:学东西不要学傻了……
233333333