安徽师大附中%你赛day7 T2 乘积 解题报告

乘积

题目背景

\(\mathrm{Smart}\) 最近在潜心研究数学, 他发现了一类很有趣的数字, 叫做无平方因子数。 也就是这一类数字不能够被任意一个质数的平方整除, 比如\(6\)\(7\)\(10\)都是无平方因子数, 而\(12\)则不是。

题目描述

所以 \(\mathrm{Smart}\) 在思考一个问题——选择不超过 \(K\)\(N\) 以内的正整数乘起来, 使得乘积是一个无平方因子数, 有多少种取法? (每个数只能取一次)

输入输出格式

输入格式

第一行一个整数 \(T\) 表示数据组数。
接下来 \(T\) 行, 每行两个整数 \(N\),\(K\), 意思如题面所述。

输出格式

对于每一组数据, 输出一个整数表示取法的方案数对 \(10^9+7\) 取模后的数值。

说明

\(10\%\)的数据: \(N≤8\)

\(40\%\)的数据: \(N≤16\)

\(70\%\)的数据: \(N≤30\)

\(100\%\)的数据: \(1≤T≤5\)\(1≤K≤N≤500\)


70pts 有非常多种搞法,然而状压是最难写的但是最可能继续玩出正解的。。

可是我太菜,比赛时写了个麻烦的状压

\(dp[i][j][s]\)代表前\(i\)个数选择了\(j\)个素数状态为\(s\)的方案数

Code:

#include <cstdio>
#include <cstring>
const int N=502;
int num[30][10],dat[30];
int pri[N],is[N],v[N],cnt,tot;
int div[N][100];
void init()
{
    for(int i=2;i<=500;i++)
    {
        if(!is[i])
        {
            pri[++cnt]=i;
            v[i]=i;
        }
        for(int j=1;j<=cnt&&i*pri[j]<=500;j++)
        {
            if(v[i]<pri[j]) break;
            v[i*pri[j]]=pri[j];
            is[i*pri[j]]=1;
        }
    }
    for(int i=2;i<=500;i++)
    {
        int t=i;
        for(int j=1;j<=cnt;j++)
            while(t%pri[j]==0)
                div[i][j]++,t/=pri[j];
    }
    for(int i=2;i<=33;i++)
    {
        int flag=1;
        for(int j=1;j<=12;j++)
            if(div[i][j]>1) {flag=0;break;}
        if(!flag) continue;
        ++tot;
        dat[tot]=i;
        for(int j=1;j<=12;j++)
            if(div[i][j])
                num[tot][j]=1;
    }
}
int mod=1e9+7;
int n0,k;
int dp[20][20][1200];
void work()
{
    scanf("%d%d",&n0,&k);
    int l=0,n=0;
    for(int i=1;;i++)//素数长度上界
    {
        if(pri[i]<=n0) ++l;
        else break;
    }
    for(int i=1;;i++)//选数个数上界
    {
        if(dat[i]<=n0) ++n;
        else break;
    }
    k=(k<=n?k:n);
    memset(dp,0,sizeof(dp));
    for(int i=0;i<=n;i++) dp[i][0][0]=1;
    int ans=0;
    for(int i=1;i<=n;i++)//前i个数
        for(int j=1;j<=k;j++)//取了j个
            for(int s=1;s<1<<l;s++)//素数集合状态
            {
                int las=s,flag=1;
                dp[i][j][s]=dp[i-1][j][s];
                for(int q=1;q<=l;q++)
                    if(num[i][q])//如果这一位是1
                    {
                        if((s>>q-1)&1) las^=1<<q-1;//变成0
                        else {flag=0;break;}
                    }
                if(flag) (dp[i][j][s]+=dp[i-1][j-1][las])%=mod;
            }
    for(int i=1;i<=k;i++)
        for(int s=1;s<1<<l;s++)
        {
            if(i!=k) (ans+=dp[n][i][s]<<1)%=mod;
            else (ans+=dp[n][i][s])%=mod;
        }
    printf("%d\n",ans+1);
}
int main()
{
    freopen("mult.in","r",stdin);
    freopen("mult.out","w",stdout);
    init();
    int t;
    scanf("%d",&t);
    while(t--)
        work();
    return 0;
}

而正解只是运用分组背包的思想

注意到大于19的质数只可能出现一个

那我们实际上就只需要找到状压2,3,5,7,11,13,17,19这几个素数就行了

其他大于19的素数按照这个素数进行分组,剩下的同时按前几个素数做就可以了

很巧妙的思想,然而数据不好造,答案一样的期望炒鸡高

Code:

#include <cstdio>
#include <cstring>
#include <vector>
#define ll long long
const int N=500;
const ll mod=1e9+7;
const int pri[9]={0,2,3,5,7,11,13,17,19};
using namespace std;
vector <int > g[N+10];
int belong[N+10],sta[N+10],n,k;
void init()
{
    memset(belong,0,sizeof(belong));
    memset(sta,0,sizeof(sta));
    for(int i=1;i<=N;i++) g[i].clear();
    for(int i=1;i<=n;i++)
    {
        belong[i]=i;
        for(int j=1;j<=8;j++)
        {
            if(i%(pri[j]*pri[j])==0) {sta[i]=-1;break;}
            else if(i%pri[j]==0) {belong[i]/=pri[j];sta[i]|=1<<j-1;}
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(~sta[i])
        {
            if(belong[i]!=1)
                g[belong[i]].push_back(sta[i]);
            else
                g[i].push_back(sta[i]);
        }
    }
}
ll dp[N+10][256];
void work()
{
    scanf("%d%d",&n,&k);
    init();
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=k;j;j--)
            for(int s=255;~s;s--)
            {
                if(!dp[j][s]) continue;
                for(int l=0;l<g[i].size();l++)
                {
                    int now=g[i][l];
                    if(now&s) continue;
                    (dp[j][now|s]+=dp[j-1][s])%=mod;
                }
            }    
    }
    ll ans=0;
    for(int i=1;i<=k;i++)
        for(int s=0;s<=255;s++)
            (ans+=dp[i][s])%=mod;
    printf("%lld\n",ans);
}
int main()
{
    //freopen("mult.in","r",stdin);
    //freopen("mult.out","w",stdout);
    int t;scanf("%d",&t);
    while(t--)
        work();
    return 0;
}

2018.8.20

猜你喜欢

转载自www.cnblogs.com/ppprseter/p/9507033.html
今日推荐