source:
题意:将一个数S分解成若干个整数之和,每一种分法对应一个最小公倍数,求能取到的最大的最小公倍数。
思路:先尝试分一下,发现若其中两数不互素,那么其不互素的部分相当于“浪费”掉了,完全可以将两数调整成互素的,把公因子提出来,这样可以形成新的方案,不难证明此新方案不可能比原方案差,由此得到肯定存在两两互素的最佳方案。进一步不难证明分出的树是单独的素数的k次方可以是最优的。至此问题已然变成了一个背包问题,完全背包问题,每个物品可以无限选,每次只需枚举物品个数k,而其花费即为p^k(p为当前枚举到的素数),价值即为乘p^k。
注:
1、这里题目中提示了数目过大会需要取余操作,但是由于我们记录的是最大值,不能在运算过程中取余,除非我们取个log!将乘法化为加法,这样就不会爆int,例外专门开个数组记录真正取余的答案,而用判断的是double类型取了log的数。
2、这里不能直接将重复物品单独算,因为重复物品的价值是跟k(重复个数)有关系的!故只能枚举k!按普通背包那样求解
3、用素数筛法进行初始化!
4、又一次犯memset的错误!memset只能赋值0和-1,1是不行的,原因在于他是将前多少位都赋值成相同的ascii码,0和-1都没问题,但是其他就会出问题!
代码如下:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
int vis[3005],num,p[3000],ans[3010];
double dp[3010];
void cpnl() // cpnl means creat prime number list
{
memset(vis,0,sizeof(vis));
num=0;
for(int i=2;i<3000;i++)
{
if(!vis[i])
{
p[num++]=i;
for(int j=i;j<=3000;j+=i) vis[j]=1;
}
}
}
int main()
{
int s,m,pk;
cpnl(); //初始化:素数筛法
//printf("%d\n",num);
while(~scanf("%d%d",&s,&m))
{
memset(dp,0.0,sizeof(dp));
for(int i=0;i<=s;i++)
ans[i]=1;
for(int i=0;i<num;i++) //No. of package
for(int j=s;j>0;j--) //限制
{
for(int k=1,pk=p[i];pk<=j;k++,pk=pk*p[i])
if(dp[j-pk]+k*log(p[i]) > dp[j])
{
dp[j]=dp[j-pk]+k*log(p[i]);
ans[j]=ans[j-pk]*pk % m;
//printf("%d,%d : %d : %d %d\n",i,j,ans[j],pk,ans[4]);
}
}
printf("%d\n",ans[s]);
}
return 0;
}