题目链接: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 }
浙公网安备 33010602011771号