HDU 4427 Math Magic (2012年长春现场赛H题)

该博客介绍了如何解决HDU 4427 Math Magic问题,该问题要求找到k个正整数,其和为N,最小公倍数为M的解的数量。通过利用状态压缩的思想,将问题转化为求解选择i个数,它们的和为j,不同素因子集合为s的解的数量。通过刷表法更新动态规划状态,最终计算dp(k, N, M的素因子全集)来得到答案。博客还提供了问题的解题思路和代码实现。" 100540704,7373470,PAT甲级题目1027:ZigZagging on a Tree的解题思路,"['数据结构', '树', '算法', 'PAT甲级', '竞赛编程']
摘要由CSDN通过智能技术生成

1.题目描述:点击打开链接

2.解题思路:本题要求寻找k个正整数,它们的和恰好是N,它们的LCM恰好是M的解的个数。可以设置一个三维的dp来解决。用dp(i,j,k)表示选择i个数,它们的和恰好是j,它们的LCM恰好是k的个数。那么答案就是dp(k,n,m)。不过这里介绍一种利用状态压缩思想求解的方法。


通过题意可以发现,N,M的范围都比较小,不超过1000,而1000之内的所有数的不同素因子的种类数目不超过4个,这是因为2*3*5*7<1000,而2*3*5*7*11>1000。考虑到素因子种类数非常少的特点,我们可以考虑状态压缩。设dp(i,j,s)表示选择i个数,它们的和恰好是j,它们的不同素因子(这里考虑的素因子都是它的幂次恰好和M分解后对应的幂次相等的那个素因子)构成的集合为s时的解的个数。可以利用刷表法来求解,刷新公式如下:

dp(i,j+d[i],k|s[i])=dp(i,j+d[i],k|s[i])+dp(i-1,j,k);

上式中,d[i]表示M的第i个约数,s[i]表示第i个约数的“合格”的素因子构成的集合。假设M一共有L种不同的素因子,那么,k个整数的LCM恰好等于M就等价于dp(k,N,2^L-1)。2^L-1就是这L种素因子构成的集合的全集。这样,本题便可以被顺利的解决。

3.代码:

#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define me(s)  memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int, int> P;

const int MOD=1e9+7;

const int N=1050;

int dp[110][N][1<<4];
int primes[N];
int vis[N];
int idx;


void init() //筛素数
{
    me(vis);
    int m=sqrt(N+0.5);
    for(int i=2;i<=m;i++)
        if(!vis[i])
        for(int j=i*i;j<N;j+=i)
        vis[j]=1;
    for(int i=2;i<N;i++)
        if(!vis[i])
        primes[idx++]=i;
}

int main()
{
    init();
    int n,m,k;
    int num[5],p[5],d[233],type[233]; //p[i]表示M的第i个素因子,num[i]表示该素因子的幂次,d[i]表示M的第i个约数,type[i]表示第i个约数的“合格的”素因子构成的集合
    int l,tot;                        //l表示M一共有l种不同的素因子,tot表示M一共有tot个约数
    while(~scanf("%d%d%d",&n,&m,&k))
    {
        l=0;
        for(int i=0,j=m;primes[i]<=j;i++)//对M进行分解
            if(j%primes[i]==0)
        {
            p[l]=primes[i];
            num[l]=0;
            while(j%primes[i]==0)j/=primes[i],num[l]++;
            l++;
        }
        tot=0;
        for(int i=1;i<=m;i++) //寻找M的所有约数
            if(m%i==0)
        {
            d[tot]=i;
            type[tot]=0;
            int tmp=i,cnt;
            for(int j=0;j<l;j++)
                if(tmp%p[j]==0)
            {
                cnt=0;
                while(tmp%p[j]==0)tmp/=p[j],cnt++;
                if(cnt==num[j])  //如果该约数分解后,某一项的幂次和M对应的幂次相等,说明是一个合法的素因子,加入集合
                    type[tot]|=1<<j;
            }
            tot++;
        }
        int up=1<<l; //up表示M的不同素因子构成的全集
        me(dp);
        for(int i=0;i<tot&&d[i]<=n;i++)
            dp[1][d[i]][type[i]]=1;  //刷新时候需要的基础的解
        for(int i=2;i<=k;i++)
            for(int j=1;j<=n;j++)
            for(int k=0;k<up;k++)//枚举所有集合
            if(dp[i-1][j][k])
            for(int kk=0;kk<tot&&d[kk]+j<=n;kk++)//枚举所有可能的约数,进行刷新
            dp[i][j+d[kk]][k|type[kk]]=( dp[i][j+d[kk]][k|type[kk]]+dp[i-1][j][k])%MOD;
        printf("%d\n",dp[k][n][up-1]);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值