题意:
给定一个自然数x,让你给出一种拆分方式n=a1+a2+...(ai≠aj),使得每个小部分的乘积s=a1*a2*...最大
解题思路:
我们要乘积最大,那么我们把n尽可能的拆分,如果题目不要求ai≠aj,那么我们将x拆分成什么最好呢?显然拆成2和3(2为偶数,3为奇数,2x+3y可以表示大于1的所有正整数)是最好的,顺便提下3最多的时候最优,为什么不是2,将,6拆成3个2乘积为8,而将6拆成3乘积为9,(不会证)
那这样我们就把n分成x个2和y个3,如果k=n-2x-3y>0&&k<2怎么办,此时k=1,把k放在哪里可以得到最优解呢?
把k放在2上可以得到最优解。假设n=6,那么拆成一个2一个3,k=1,将k放在2上(2+k)*3增加了3*k,如果放在3上(3+k)*2增加了2*k,
那么现在题目是要求ai≠aj
我们将n分成2,3,4,5,6,7,8,。。。。。从2开始以递增分(可以分的话)
我们设sum[max]为前缀和数组,那么当sum[i]>n时,k=n-sum[i-1];
将k怎么放呢?(把k看成k个1)
我们先放第一个1,放在谁哪里呢?显然放在2那,前面证过,所以为了最优我们尽可能的放在2,
但当2+k<i,那么就会出现拆分的数重复出现的情况,所以能将k全部放在2是在2+k>=i的情况下
如果在2+k<i的情况下该怎么放,因为此时2+k<i所以放任何个数的1都会出现重复的数,此时我们就要考虑3了,如果3+k>=i,我们全部放在3,
如果3+k<i,我们考虑4,以此规则继续。。。。
我们现在知道了,该怎么放多出来的k(如果有的话),那么最优乘积怎么求呢?
我们要除掉k放在的那个数i,再乘以(k+i)
为了让前缀乘不爆掉long long,我们取余,为了取余后不影响结果我们用乘法逆元来求最优乘积。
用二分查找找i
以下是代码实现
1 #include<iostream>
2 #include<cstdio>
3 #include<algorithm>
4 #define max 1000000000
5 const __int64 MOD=1000000007;//不能用define定义MOD否则会出错,define是一个函数
6 using namespace std;
7 __int64 f[45000];
8 int sum[45000];
9 int inv[45000];
10 void del()
11 {
12 inv[1]=1;f[1]=1;sum[1]=0;
13 for (int i = 2; i<45000; i++)
14 {
15 inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;//乘法逆元
16 f[i]=(f[i-1]*i)%MOD;//前缀乘(在取余MOD的环境下,配合后面的乘法逆元)
17 sum[i]=sum[i-1]+i;//前缀和(从2开始)
18 }
19 //printf("%d %d %d xxx
",inv[2],inv[3],inv[1]);
20 return;
21 }
22 int main()
23 {
24 int T,n,i,j,k,l,r,mid;__int64 ans;
25 while(scanf("%d",&T)!=EOF)
26 {
27 del();
28 while(T--)
29 {
30 scanf("%d",&n);
31 if(n<5)printf("%d
",n);
32 else
33 {
34 l=2;r=45000;mid=(l+r)>>1;
35 while(l+1<r)
36 {
37 if(sum[mid]>n)r=mid,mid=(r+l)>>1;//r定义为开,不取状态
38 else l=mid,mid=(r+l)>>1;//l定义为闭,取状态
39 }
40 k=n-sum[l];//printf("%d %d %d xx",sum[l],k,inv[l+1-k]);
41 if(2+k>l)ans=f[l]*inv[2]%MOD*(k+2)%MOD,printf("%I64d
",(ans+MOD)%MOD);
42 else
43 ans=f[l]*inv[l+1-k]%MOD*(l+1)%MOD,printf("%I64d
",(ans+MOD)%MOD);
44 }
45 }
46 }
47 return 0;
48 }