【dp】6146 - Ultimate Device 2012合艾赛区F题
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4157
非常遗憾的一道题,比赛时候很早想出来,但是由于某些原因最后拖到绝杀时刻,且被催的绝杀TLE,没有通过。
题意:给出n(<=100)个数字,大小在1~500内,每次取任意数字,一共有2^n种方案,对每种方案中被取出数字的最小公倍数求和。结果MOD 10007.
这个问题可以得到一个非常暴力的dp, 对于一个最小公倍数显然可以表示成这种形式:p1^i1* p2^i2 * ...pk^ik,其中p是小于500的质数。我们可以把i1~ik压缩一下然后做成每个数取于不取的背包。但是,500以内的质数非常多,针对这个问题我当时想出一个优化,如果不考虑p^2>500这种质数,则只剩下2,3,5,7,11,13,17,19,其中每个质数出现次数最多是8,5,3,3,2,2,2,压缩一下此时的状态数就只有9*6*4*4*3*3*3*3=69984,正好在题目要求复杂度内。
接下来讨论如何忽略p^2>500这种质数,直接讲我的做法,我们dp过程种dp[i][sta],表示的是前i个数任意取后,最小公倍数表示成sta的方案是多少种,如果后面已经不再含有这种质数(A),且我们可以知道哪些方案至少包含一个含有这种质数p(B),则只需要将这些方案数*p即可,由于条件A,所以我们不再关心哪些方案是已经含有质数p了,但是已经含有质数p的方案权重是没有含有p的p倍,所以需要条件B。到这里做法就出来了,就是把含有这种质数的数字放到连续的一段内,假设这一段是i+1~j...则dp[j][sta]-dp[i][sta]便是至少包含一个质数p的方案,所以此时额外转移如下:dp[j][sta] = (dp[j][sta]-dp[i][sta])*p + dp[i][sta]
最后附程序:
1 //By Lin 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 #define sqr(x) ((x)*(x)) 7 #define MOD 10007 8 using namespace std; 9 10 int prime[100],cnt; 11 int num[8],n,data[105]; 12 int chuan[70000][501],tol; 13 int val[70000],dp[2][70000],tmp[70000]; 14 vector<int> g[100]; 15 16 void init(){ 17 cnt = 0; 18 for (int i = 2; i<=500; i++){ 19 bool flag = true; 20 for (int j = 0; j<cnt && sqr(prime[j])<=i; j++) 21 if ( i%prime[j] == 0 ) { flag = false; break; } 22 if ( flag ) prime[cnt++] = i; 23 } 24 tol = 1; 25 for (int i = 0; i<8; i++){ 26 num[i] = 0; int k = 1; 27 while ( k<500 ) k *= prime[i], num[i]++; 28 tol *= num[i]; 29 } 30 int g[501][8]; 31 for (int i = 1; i<=500; i++) 32 for (int j = 0; j<8; j++){ 33 g[i][j] = 0; int k = 1; 34 while ( i%(k*prime[j])== 0 ) g[i][j]++,k*=prime[j]; 35 } 36 for (int i = 0; i<tol; i++ ) { 37 int now[8]; 38 for (int j = 0 , x = i; j<8; j++) now[j] = x%num[j] , x/= num[j]; 39 val[i] = 1; 40 for (int j = 0; j<8; j++) 41 for (int k = 0; k<now[j]; k++) val[i] = val[i]*prime[j]%MOD; 42 for (int j = 1; j<=500; j++){ 43 chuan[i][j] = max( now[7] , g[j][7] ); 44 for (int k = 6; k>=0; k--){ 45 chuan[i][j] *= num[k]; 46 chuan[i][j] += max( now[k] , g[j][k] ); 47 } 48 } 49 } 50 } 51 52 int main(){ 53 init(); 54 int cas ,tt = 0; 55 scanf("%d", &cas ); 56 while ( cas -- ) { 57 scanf("%d", &n ); 58 for (int i = 0; i<cnt; i++) g[i].clear(); 59 for (int i = 0; i<n; i++) { 60 int x; 61 scanf("%d", &x ); 62 bool flag = true; 63 for (int j = 8; j<cnt; j++) 64 if ( x%prime[j] == 0 ) { 65 flag = false; 66 g[j].push_back(x); 67 break; 68 } 69 if ( flag ) g[0].push_back(x); 70 } 71 int p = 0 , q = 1; 72 memset( dp , 0 , sizeof(dp) ); 73 dp[0][0] = 1; 74 for (int i = 0; i<cnt; i++) { 75 if ( g[i].size() == 0 ) continue; 76 if ( i ) memcpy( tmp , dp[p] , sizeof(dp[p]) ); 77 for (int j = 0; j<g[i].size(); j++ ) { 78 int x = g[i][j]; 79 memcpy( dp[q] , dp[p] , sizeof(dp[p]) ); 80 for (int k = 0; k<tol; k++) { 81 if ( dp[p][k] == 0 ) continue; 82 dp[q][ chuan[k][x] ] += dp[p][k]; 83 dp[q][ chuan[k][x] ] %= MOD; 84 } 85 swap(p,q); 86 } 87 if ( i ) { 88 for (int k = 0; k<tol; k++) 89 if ( dp[p][k] || tmp[k] ) dp[p][k] = ((dp[p][k]-tmp[k]+MOD)*prime[i]+tmp[k])%MOD; 90 } 91 } 92 int ans = 0; 93 dp[p][0] += MOD-1; 94 dp[p][0] %= MOD; 95 for (int k = 0; k<tol; k++){ 96 ans += dp[p][k]*val[k]; 97 ans %= MOD; 98 } 99 printf("Case %d: %d\n" , ++tt , ans ); 100 101 } 102 return 0; 103 }