[NOIP模拟][状压DP]乘积

题目描述:
题目大意:
给出n和k,求从小于等于n的数中取出不超过k个,其乘积是无平方因子数的方案数。无平方因子数:不能被质数的平方整除。
样例输入:

3
1 1
6 4
4 2

样例输出:

1
19
6

题目大意:
状压dp+分组背包
1~n中每个数含有的大于 n 的质因数最多有1个,而 n=500 内的质数只有8个:2,3,5,7,11,13,17,19。考虑这样将1~n的数分组,含有大于19的质因子数分为一组(如 23,46,69一组,如29,58一组),其余的单独成组。组内的每个元素用8位二进制表示该数含有的小于等于19的质因子。这样分组是因为小于等于n的质数只有8个可以方便的进行状压dp,而含有大于19的质因子的数难以在有限空间内状压,于是将其放在一个组内,dp时就只取出该组中的一个数,避免了乘积大于19的质因子。
因为代码用了vector,vector调用比较慢,出题人又专门卡常,所以需要一些优秀的卡常技巧。
附代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;

const int N=600;
const int M=(1<<8)-1;
const int mod=1e9+7;
const int prime[8]={2,3,5,7,11,13,17,19};
int f[N][M+10],num[N],exist[N],n,k,t,temp[N],ans;
vector<int> g[N];

int readint()
{
    char ch;int i=0,f=1;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') {ch=getchar();f=-1;}
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<3)+(i<<1)+ch-'0';
    return i*f;
}

void prework()
{
    for(int i=1;i<=n;i++)
    {
        int now=i;exist[i]=true;
        for(int j=0;j<8;j++)
        if(now%(prime[j]*prime[j])==0)//判断此数是否符合是无平方因子数
        {
            exist[i]=false;
            break;
        }
        else
            if(now%prime[j]==0)//表示这个数含有小于等于19的质因子
                now/=prime[j],num[i]|=(1<<j);//记录状态,包含哪些小于等于19的质因子
        if(exist[i])
        {
            if(now==1) g[i].push_back(i);//不含大于19的质因子,单独成组
            else g[now].push_back(i); //前面每次都在除,还有剩余,说明含大于19的质因子,于是分到质因子那组
        }
    }
}

void add(int &x,int t)//为了卡常
{
    x+=t;
    if(x>=mod) x-=mod;                                    
}

int main()
{
    //freopen("mul.in","r",stdin);
    //freopen("mul.out","w",stdout);

    t=readint();
    while(t--)
    {
        n=readint();k=readint();
        memset(f,0,sizeof(f));
        for(int i=1;i<=n;i++) g[i].clear();
        prework();
        f[0][0]=1;
        for(int i=1;i<=n;i++)//其实是在枚举每一个分组
            if(exist[i]&&g[i].size()!=0)//此数合法且此组不为空
            {
                for(int j=g[i].size()-1;j>=0;j--)//为了卡常
                    temp[j]=num[g[i][j]];
                for(int l=k-1;l>=0;l--)//倒序,因为下面的更新,是把上一层的更新到现在的i,正序会让这一层覆盖掉上一层的结果
                    for(int j=g[i].size()-1;j>=0;j--)//枚举这一组
                        for(int s=(M^temp[j]),t=s;;s=((s-1)&t))//s表示状态,第一节是指只不选temp[j],第三节是在跳s的每个子集
                        {
                            add(f[l+1][s|temp[j]],f[l][s]);//f[l][s]存的是指上一层计算出来的结果
                            if(s==0) break;//子集为空break,之所以不放在for循环第二节,是因为我们需要更新一次子集为空的转移
                        }
            }
        ans=0;
        for(int i=1;i<=k;i++)//统计各种情况和
            for(int j=0;j<=M;j++)
                add(ans,f[i][j]);
        cout<<ans<<'\n';//printf("%d\n",ans);
    }

    return 0;
}
发布了99 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qianguch/article/details/78335422