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("
");
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 }
View Code

 

2.FFT

好吧表示这个做法是lrd一开始想的……然而他并不会FFT

定义f[i][j]表示用前i个物品凑出j的方案数,g[i][j]表示用后i个物品凑出j的方案数,h[i][j]表示去掉i后凑出j的方案数,显然有

egin{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("
");
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 }
View Code

 

3.DP(想不到更好的称呼了……)

这个做法是lrz告诉我的……我怎么这么愚蠢连这都想不到

首先把用所有物品凑出j的方案数全都求出来,因为填满j的方案数不受物品顺序影响,而把第i个物品放到最后之后是可以把DP方程逆变换回去得到删掉i之后凑出j的方案数,然后就果断O(nm)了……咦怎么才比多一个logCDQ分治快这么一点

 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("
");
25     }
26     return 0;
27 }
View Code

这个故事告诉我们一个道理:学东西不要学傻了……

原文地址:https://www.cnblogs.com/hzoier/p/6410286.html