[NOIP2017模拟]乘积

2017.10.24 T2 2008

样例数据
输入

3
1 1
6 4
4 2

输出

1
19
6

分析:状压DP+分组背包
每一个数只会含有一个比sqrt(N)大的质因数,所以我们把所有数按照有比sqrt(N)大的数分组,
sqrt(N)以内的质数只有8个。
然后dp[i][j][mask]表示计算完了前i组数,选择了j个数,mask表示当前乘积含有比sqrt(N)小的质数的集合。
然后进行背包就好了。
这道题70%的档本来是给想到状压dp但是没有用背包优化的代码的,但是30以内的情况最多就只有6655个,跑个剪枝dfs也能过,打表也能过。
但是我的代码有毒,在windows环境下编译没问题,到linux环境就爆数组了!看来考试的时候真的要开一下虚拟机跑一跑……活活又少了70分,这样,这次考试就只有0+0+20=20,爆炸得不行,必须熟悉一下linux环境有哪些Do’s and Don’ts了。

代码:
70%:暴搜剪枝

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

int getint()
{
    int sum=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;isdigit(ch);ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;
    return sum*f;
}

const int mo=1e9+7;
int T,n,k,ans;
int cnt,zhishu[100],num[510],mul[100];
bool bj[510];

void pre()
{
    for(int i=2;i<=500;++i)
    {
        if(!bj[i])
        {
            zhishu[++cnt]=i;
            for(int j=2;i*j<=500;++j)
                bj[i*j]=true;
        }

        num[i]=cnt;
    }

    for(int i=1;i<=cnt;++i)
        mul[i]=zhishu[i]*zhishu[i];//算每个质数的平方
}

bool check(long long w)
{
    for(int i=1;i<=cnt;++i)//看看这个数能否被某个质数的平方整除
        if(w%mul[i]==0)
            return false;
    return true;
}

void dfs(int fa,int step,long long w)//fa:前一个数,step:第几个,w:前面数的乘积
{
    if(step>k)
        return;

    for(int i=fa+1;i<=n;++i)
    {
        if(check(i*w))//只有还满足题意的情况才能往下dfs
        {
            ans=(ans+1)%mo;
            dfs(i,step+1,i*w);
        }
    }
}

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

    pre();//找质数
    T=getint();
    while(T--)
    {
        ans=0;
        n=getint(),k=getint();
        if(n<=30)
        {   
            dfs(0,1,1);
            cout<<ans<<'\n';
        }
        else
            cout<<rand()%mo<<'\n';//大于直接随机了hhh
    }
    return 0;
}

100%:状压dp+多组背包

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

int getint()
{
    int sum=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;isdigit(ch);ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;
    return sum*f;
}

const int maxn=505;
const int maxm=(1<<8)-1;
const int MAX=maxm+5;
const int mod =1e9+7;
int dp[maxn][MAX],num[maxn],exist[maxn];
int T,n,k,prime[8]={2,3,5,7,11,13,17,19};
vector<int> g[maxn]; 

void init()
{
    for(int i=1;i<=n;++i)
    {
        int now=i;
        exist[i]=1;
        for(int j=0;j<8;++j) 
            if(now%(prime[j]*prime[j])==0)//把那些本来就含有质数平方的数剔除
            {
                exist[i]=0;
                break;
            }
            else if(now%prime[j]==0)//二进制标记含有哪些质因子
                now/=prime[j],num[i]|=(1<<j);
        if(exist[i])//满足的数放到vector数组中,也就是背包,余下什么就在哪个背包里
        {
            if(now==1) g[i].push_back(i);//只剩下1的就放在自己本身位置背包
            else g[now].push_back(i);//这是有质数大于sqrt(N)的情况,就放在对应的质数位置背包里
        }
    }
}

void add(int &x, int t)//为什么不写成(x+t)%mod?因为也会被卡常,这样快一些
{
    x=x+t;
    if(x>=mod)
        x-=mod;
    else
        x-=0;
}

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

    T=getint();
    while(T--)
    {
        n=getint(), k=getint();
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;++i) g[i].clear();
        init();
        dp[0][0]=1;//我写的dp省略了第i组那一维,不影响
        for(int i=1;i<=n;++i)
            if(exist[i]&&g[i].size()!=0)//背包dp,看这组是否合法
            {
                int temp[maxn];
                for(int j=g[i].size()-1;j>=0;--j)
                    temp[j]=num[g[i][j]];//这步操作只是把这一组的所有数从vector数组里拿出来,因为调用vector很慢,被卡常
                for(int l=k-1;l>=0;--l)//从后往前更新,因为上一个数更新了所有的状态,如果这个数从前往后就把前一个数更新的值覆盖了,变成自己更新自己了,所以要从后面开始更新
                    for(int j=g[i].size()-1;j>=0;--j)//枚举该组所有成分
                        for(int s=(maxm^temp[j]),t=s;;s=((s-1)&t))//s是和现在枚举的成分没有公共质因数的状态,更新s及其每个子集
                        {
                            add(dp[l+1][s|temp[j]],dp[l][s]);//更新状态
                            if(s==0) break;
                        }
            }
        int ans=0;
        for(int i=1;i<=k;++i)
            for(int j = 0;j<=maxm;++j)
                add(ans,dp[i][j]);//从1—k个数所有情况加起来得ans
        cout<<ans<<'\n';
    }

    return 0;
} 

本题结。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值