题意:给定一个数,让你分成互不相等的n个数(n为自然数),使这些数的乘积最大,输出最大乘积。
题解:本文参考传送门
首先:那就是不能分出1来,因为1乘任何数都是它本身,而因为分出了1,另一部分也变小了,白白使整个乘积都变小了
第二:尽量将数n分成连续的数之和能使得乘积最大即2,3,4.....r之积
假设将数n分成两个数之和r1,r2那么如果把r1再次分解成非1的两个数相加r11,r12那么这两个数的乘积一定大于r1,对于r2也是这样。因此我们只要可以不断分解下去,那么分解后的肯定更大,而根据题意每个数不可相同,那么最终最多就只能分解成尽量连续的数之和的形式。
为什么说是尽量呢?因为很明显,并不是所有的数都可以分解成从2开始连续的数之和的形式,大部分的数会有一个剩余部分Δx
假设我们先将数n分解成了2+3+4+...+l+Δx的形式
我们发现0≤Δx≤l
若Δx=0则说明恰好分成连续数之和,为什么必须小于等于l呢
因为如果Δx>l即 Δx≥l+1,那我们干嘛还把l+1放在Δx中呢,我们肯定就得到了2,3,...l,l+1了所以0≤Δx≤l
根据0≤Δx≤l,为了让乘积最大,不能把Δx单独作为一个数和其他部分相乘,因为它的范围肯定和那些连续的数有重复
要尽可能分解,那么我们就把它分成1,分给那些连续的数,为了使乘积最大,我们应该把1从后往前分,因为从前往后分,很容易分完后发现出现了重复元素(即并不够分给每一个数一个1的情况,那么必将和下一个数相同)
这样思路就清楚了,先找最大的小于等于n的连续数之和,然后剩下的部分从后往前一个一个分配。
这样分配后得到两种情况:
-
Δx=l,因为我们分出的连续的数是2,3,4...l一共有l-1个数,那么说明l分完一圈后还剩下1个1,我没再把这个1分给最后一个,这样就得到数列
3,4,5....l,l+2 -
其他情况从后往前分得到数列
2,3,4...k,k+2...l,l+1
即中间有一个相差2的分解处
当然了如果Δx=l−1则每个数恰好分得一个1,是这种情况的一种特殊情况3,4,5...l,l+1和这种情况按一种方法算即可
为了优化我们当然不能每次都重新算乘积,每次一个一个的尝试把它分解成最大的小于数n的连续数
因此我们可以预处理一个从2开始的前缀和sum[i],前缀积mul[i]。这样我们二分查找小于等于n的最大前缀和就得到了分解成连续数之和的那个最大数,即上面分析中的l,n-前缀和得到剩余部分Δx.
根据我们上面分的两种情况
- Δx=l时,先得到连续数的乘积mul[l],l二分已经得到,然后比较分配后的序列,少了2,多了l+2,这样我们除去2,乘l+2即可,注意因为涉及取模故除2要变成乘2的逆元的形式
- 其他情况中间肯定有个分界处,即k,k+2,那么从2到k算作一部分,从k+2到l+1算一部分。对于2~k这部分直接乘上既可以了k=l−Δx,对于后边部分我们可以用mul[l+1]/mul[k+1]就得到了k+2~l+1,k+1=l−Δx+1
附上代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int mod=1e9+7;
typedef long long ll;
ll mul[maxn],sum[maxn];
void init()
{
mul[1]=1;
sum[1]=0;
for(int i=2;i<maxn;i++){
sum[i]=sum[i-1]+i;
mul[i]=(i*mul[i-1])%mod;
}
}
ll inv(ll a,ll b)
{
ll ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
int main()
{
init();
int t;
scanf("%d",&t);
while(t--){
ll x;
scanf("%lld",&x);
if(x<=4){
printf("%lld\n",x);
continue;
}
int l=2,r=maxn,mid,p;
while(l<=r){
mid=l+r>>1;
if(sum[mid]<=x){
p=mid,l=mid+1;
}else{
r=mid-1;
}
}
int num=x-sum[p];
ll ans=0;
if(num==p){
ans=mul[p]*inv(2,mod-2)%mod*(p+2)%mod;
}else{
ans=mul[p+1]*inv(mul[p-num+1],mod-2)%mod*mul[p-num]%mod;
}
printf("%lld\n",ans);
}
return 0;
}