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 }

 

posted @ 2018-09-24 22:13  guapisolo  阅读(182)  评论(0编辑  收藏  举报