HDU 6125 Free from square (状压DP+分组背包)
题目大意:让你在1~n中选择不多于k个数(n,k<=500),保证它们的乘积不能被平方数整除。求选择的方案数
因为质数的平方在500以内的只有8个,所以我们考虑状压
先找出在n以内所有平方数小于等于n的质数,然后我们把它们作为状压的状态
然后要对每个小于n数进行状压,如果它不能被它能被质数的平方整除,那就筛出它所有的在状态内的质因子,大于状态内的质因子我们存到剩余因子的乘积的部分里
比如46,它的状态可以表示成0000 0001 (19,17,13,11,7,5,3,2) 46/2=23,把它存到23的0000 0001状态里
这么做之后,你会惊奇的发现,每个数字只被存到了一个地方,且只被存了一次!
而这也是分组背包可行的关键
下一步就是分组背包了
我们从1遍历到n,遍历所有状态,如果存了,意味着这个状态表示的数可以选一个,就++
然后枚举上一层进行转移即可
细节可以看代码
1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4 #include <vector> 5 #define N 100 6 #define M 505 7 #define maxn (1<<8)+5 8 #define ll long long 9 #define mod 1000000007 10 using namespace std; 11 12 int T,n,m,cnt,K; 13 ll f[2][M][maxn],tmp[M][maxn]; 14 int p[M],ok[M],pr[M],can[M][maxn],use[M]; 15 int a[]={0,2,3,5,7,11,13,17,19,5000}; 16 vector<int>to[M]; 17 void clr() 18 { 19 for(int i=0;i<M;i++) to[i].clear(); 20 memset(p,0,sizeof(p)); 21 memset(ok,0,sizeof(ok)); 22 memset(pr,0,sizeof(pr)); 23 memset(can,0,sizeof(can)); 24 memset(use,0,sizeof(use)); 25 memset(f,0,sizeof(f)); 26 cnt=0; 27 } 28 void get_P() 29 { 30 for(int i=2;i<=n;i++) 31 { 32 if(!use[i]) pr[cnt++]=i; 33 for(int j=0;j<cnt&&i*pr[j]<=n;j++){ 34 use[i*pr[j]]=1; 35 if(i%pr[j]==0) break;} 36 } 37 for(int i=1;i<=n;i++) 38 { 39 int x=i,ps=0; 40 for(int j=0;j<min(K,8);j++) 41 if(x%(pr[j]*pr[j])==0) { 42 ok[i]=-1,p[i]=0; 43 break; 44 }else if(ok[i]!=-1&&x%pr[j]==0) 45 { 46 p[i]|=(1<<j); 47 x/=pr[j]; 48 } 49 if(ok[i]!=-1) 50 { 51 if(x!=1) to[x].push_back(i); 52 else to[i].push_back(i); 53 } 54 } 55 for(int i=1;i<=n;i++) //can数组表示i是否存了能用某个二进制质因子表示的数 56 { 57 if(ok[i]==-1) continue; 58 for(int j=0;j<to[i].size();j++) 59 can[i][p[to[i][j]]]=1; 60 } 61 } 62 63 int main() 64 { 65 //freopen("aa.in","r",stdin); 66 scanf("%d",&T); 67 while(T--) 68 { 69 scanf("%d%d",&n,&m); 70 clr(); 71 K=0; 72 while(K+1<=8&&a[K+1]*a[K+1]<=n) K++; 73 get_P(); 74 int y=1,x=0; 75 for(int i=1;i<=n;i++){ //分组背包 76 if(!to[i].size()) continue; 77 for(int s1=0;s1<(1<<K);s1++) 78 for(int j=1;j<=m;j++) 79 f[y][j][s1]=f[x][j][s1]; 80 for(int s1=0;s1<(1<<K);s1++) 81 { 82 if(!can[i][s1]) continue; 83 f[y][1][s1]=(f[y][1][s1]+1)%mod; 84 //如果i的状态里有s1,那么说明这个状态表示的数可以直接被选,就++ 85 for(int j=1;j<=m;j++) 86 for(int s2=0;s2<(1<<K);s2++) 87 { 88 if(s1&s2) continue; 89 f[y][j][s1|s2]+=f[x][j-1][s2]; 90 f[y][j][s1|s2]%=mod; 91 } 92 } 93 swap(y,x); 94 } 95 ll ans=0; 96 for(int s=0;s<(1<<K);s++) 97 for(int j=1;j<=m;j++) 98 ans=(ans+f[x][j][s])%mod; 99 printf("%lld\n",ans); 100 } 101 return 0; 102 }