KunKun的征途

明天的明天,你还会送我水晶之恋吗?

导航

[ZOJ 3662] Math Magic (动态规划+状态压缩)

Posted on 2014-10-21 21:08  西域小车  阅读(271)  评论(0)    收藏  举报

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3662

之前写过这道题,结果被康神吐槽说代码写的挫。

的确,那时候想法太挫了。

题意:

给你三个数n,m,k。

问你存在多少个数列 a1,a2,...,ak,使得他们的和为n,他们的最小公倍数为m。

 

想法一:

因为 lcm(a1,a2,...,ak)=m,所以a1,a2,a3,...,ak都是m的约数。

因此预处理出来m的约数,记在w[i]里。

设计状态dp[i][j][k]代表有i个数的数列,和为j,最小公倍数为k的个数。

转移:dp[i][j][k] = sigma( dp[i-1][s][t] ) 其中 s+w[i] = j,lcm( t,w[i] ) = k

于是写成 dp[i][s+w[i]][lcm(t,w[i])] = sigma( dp[i-1][s][t] )

需要优化的部分较多,需要滚动数组,很容易TLE掉。

时间复杂度O( n*m*k*sqrt(n) )

于是被康神吐槽了。。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int MOD = 1000000007;
 5 int n,m,kk;
 6 int a[1100],ptr;
 7 int lcmn[1100][1100];
 8 int dp[2][1100][1100];
 9 int ss[2][1100][1100];
10 
11 void getlcmn(){
12     for(int i=1;i<1100;i++){
13         for(int j=1;j<1100;j++){
14             lcmn[i][j] = i / __gcd(i,j) * j;
15         }
16     }
17 }
18 
19 
20 int main(){
21     getlcmn();
22     while(scanf("%d%d%d",&n,&m,&kk)!=EOF){
23         ptr = 0;
24         for(int i=1;i*i<=m;i++){
25             if( m%i==0 ){
26                 a[ptr++] = i;
27                 if( i*i!=m ) a[ptr++] = m/i;
28             }
29         }
30         //for(int i=0;i<ptr;i++) printf("%d ",a[i]); puts("");
31         memset(dp,0,sizeof(dp));
32         for(int i=0;i<=min(m,n);i++) dp[1][i][i] = 1;
33         for(int i=2;i<=kk;i++){
34             for(int j=0;j<=n;j++){
35                 for(int k=0;k<=m;k++) dp[i&1][j][k] = 0;
36                 for(int s=0;s<ptr;s++){
37                     if( j-a[s]>=0 ){
38                         for(int t=0;t<ptr;t++) {
39                             if(lcmn[a[s]][a[t]]<=m) {
40                                 dp[i&1][j][lcmn[a[s]][a[t]]] += dp[(i-1)&1][j-a[s]][a[t]];
41                                 dp[i&1][j][lcmn[a[s]][a[t]]] %= MOD;
42                             }
43                         }
44                     }
45                 }
46             }
47         }
48         printf("%d\n",dp[kk&1][n][m]);
49     }
50     return 0;
51 }
想法一

 

当时听康神说所谓的正解,还真是没听懂,正巧昨天做了这道题:http://www.cnblogs.com/llkpersonal/p/4037686.html

于是今天早上就想起这个题目,也想了想“正解”。

由题意我们知道,m是a1,a2,...,ak的最小公倍数,因此,将a1,a2,a3,...,an质因数分解,取每一个质因数的最高次幂,然后乘起来就是m。

那么我们先对m进行质因数分解,然后划分状态,假设有sn个质因数,那么总状态数就是(1<<sn)-1个。

然后去分解m的因数,给每个因数定状态,如果说因数k的质因数的指数等于m的指数,那么就给该状态标1。记作v[i]

然后去跑完全背包。

定义状态dp[i][j][k][mask]代表数列有i个数,从前j个数里选,和为k,状态为mask。

状态转移:dp[i][j][k][mask] = sigma( dp[i-1][j-1][s][t] ) 其中s+w[i] = k , t|v[i]=mask

于是有dp[i][j][s+w[i]][t|v[i]] = sigma( dp[i-1][j-1][s][t] ) 对于每一个i,都相当于是一次独立的完全背包。

由于是完全背包,因此j维可以省掉。

于是化为

dp[j][s+w[i]][t|v[i]] = sigma( dp[j-1][s][t|v[i]] )

正向递推

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <cmath>
 5 #include <map>
 6 #include <iterator>
 7 #include <vector>
 8 using namespace std;
 9 typedef long long LL;
10 
11 int n,m,k;
12 int p[1000],h[1000],w[1000],v[1000],ptr;
13 int dp[111][1100][1<<5];
14 const int MOD = 1000000007;
15 
16 void prime_factor(int x){
17     for(int i=2;i*i<=x;i++){
18         while( x%i==0 ){
19             p[i]++;
20             x /= i;
21         }
22     }
23     if( x!=1 ) p[x] = 1;
24 }
25 
26 void handle(int x){
27     w[++ptr] = x;
28     for(int i=2;i*i<=x;i++){
29         int t = 0;
30         while( x%i==0 ){
31             t ++;
32             x /= i;
33         }
34         if( t&&t==p[i] ) v[ptr] |= (1<<(h[i]-1));
35     }
36     if( x!=1&&p[x]==1 ) v[ptr] |= (1<<(h[x]-1));
37 }
38 
39 int main(){
40     while(scanf("%d%d%d",&n,&m,&k)!=EOF){
41         ptr = 0;
42         memset(p,0,sizeof(p));
43         memset(w,0,sizeof(w));
44         memset(v,0,sizeof(v));
45         memset(h,0,sizeof(h));
46         prime_factor(m);
47         int sn = 0;
48         for(int i=0;i<1000;i++){
49             if( p[i] ) {
50                 h[i] = ++sn;
51             }
52         }
53         for(int i=1;i*i<=m;i++){
54             if( m%i==0 ) {
55                 handle(i);
56                 if( i*i!=m ) handle(m/i);
57             }
58         }
59         memset(dp,0,sizeof(dp));
60         dp[0][0][0] = 1;
61         for(int j=1;j<=k;j++){
62             for(int i=1;i<=ptr;i++){
63                 for(int k=0;k<=n;k++){
64                     for(int mask=0;mask<(1<<sn);mask++){
65                         if( k+w[i]<=n&&(mask|v[i])<(1<<sn) ){
66                             dp[j][k+w[i]][mask|v[i]] = (dp[j][k+w[i]][mask|v[i]]+dp[j-1][k][mask])%MOD;
67                         }
68                     }
69                 }
70             }
71         }
72         printf("%d\n",dp[k][n][(1<<sn)-1]);
73     }
74     return 0;
75 }
想法二