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,遍历所有状态,如果存了,意味着这个状态表示的数可以选一个,就++

然后枚举上一层进行转移即可

细节可以看代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define N 100
#define M 505
#define maxn (1<<8)+5
#define ll long long 
#define mod 1000000007
using namespace std;

int T,n,m,cnt,K;
ll f[2][M][maxn],tmp[M][maxn];
int p[M],ok[M],pr[M],can[M][maxn],use[M];
int a[]={0,2,3,5,7,11,13,17,19,5000};
vector<int>to[M];
void clr()
{
    for(int i=0;i<M;i++) to[i].clear();
    memset(p,0,sizeof(p));
    memset(ok,0,sizeof(ok));
    memset(pr,0,sizeof(pr));
    memset(can,0,sizeof(can));
    memset(use,0,sizeof(use));
    memset(f,0,sizeof(f));
    cnt=0;
}
void get_P()
{
    for(int i=2;i<=n;i++)
    {
        if(!use[i]) pr[cnt++]=i;
        for(int j=0;j<cnt&&i*pr[j]<=n;j++){
            use[i*pr[j]]=1;
            if(i%pr[j]==0) break;}
    }
    for(int i=1;i<=n;i++)
    {
        int x=i,ps=0;
        for(int j=0;j<min(K,8);j++)
            if(x%(pr[j]*pr[j])==0) {
                ok[i]=-1,p[i]=0;
                break;
            }else if(ok[i]!=-1&&x%pr[j]==0)
            {
                p[i]|=(1<<j);
                x/=pr[j];
            } 
        if(ok[i]!=-1)
        {
            if(x!=1) to[x].push_back(i);
            else     to[i].push_back(i);
        } 
    }
    for(int i=1;i<=n;i++) //can数组表示i是否存了能用某个二进制质因子表示的数
    {
        if(ok[i]==-1) continue;
        for(int j=0;j<to[i].size();j++)
            can[i][p[to[i][j]]]=1;
    }
}

int main()
{
    //freopen("aa.in","r",stdin);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        clr();
        K=0;
        while(K+1<=8&&a[K+1]*a[K+1]<=n) K++;
        get_P();
        int y=1,x=0;
        for(int i=1;i<=n;i++){ //分组背包
            if(!to[i].size()) continue;
            for(int s1=0;s1<(1<<K);s1++)
                for(int j=1;j<=m;j++)
                    f[y][j][s1]=f[x][j][s1];
            for(int s1=0;s1<(1<<K);s1++) 
            {
                if(!can[i][s1]) continue;
                f[y][1][s1]=(f[y][1][s1]+1)%mod; 
            //如果i的状态里有s1,那么说明这个状态表示的数可以直接被选,就++
                for(int j=1;j<=m;j++)
                    for(int s2=0;s2<(1<<K);s2++)
                    {
                        if(s1&s2) continue;
                        f[y][j][s1|s2]+=f[x][j-1][s2];
                        f[y][j][s1|s2]%=mod;
                    }
            }
            swap(y,x);
        }    
        ll ans=0;  
        for(int s=0;s<(1<<K);s++)
            for(int j=1;j<=m;j++)
                ans=(ans+f[x][j][s])%mod;
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/guapisolo/article/details/81805262
今日推荐