寒假Day54:HDU5976-Detachment-前缀和+前缀积+逆元(费马小定理)+二分
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=5976
题意:
把一个数拆成几个不相同的数,求这些数的乘积的最大值。
思路:
使一个数n乘积最大,拆成两个数x、y ---> 则x、y接近n/2,再去进行拆分;
但是题意给的是不相同的多个数,所以最优解则是 ---> (2+3+...+k)=n,要是从2开始且连续的;
所以我们开一个sum数组去处理一下前缀和;
既然如此,那么如果找到了最优解,那么还会有剩下的数 ---> yu = n - sum [ k ],
但是别忘了sum是从2开始存储的,所以预处理的时候需要sum [ 0 ] = sum [ 1 ] = 0;
如何处理剩下的数yu呢?
这个数应该加到2+3+...+k的其中一个数字上去,
(这个道理我不知道为什么)给到最小的数字上面去乘积就是最大的,但是要保证加上最小的数字之后也不存在重复,
所以给到的就是 ---> q = k + 1 - yu ,找q需要二分,因为数据比较大+是有序的
所以ans =ji[k]/q*(q+yu) ---> ji:前缀积数组
除法用逆元 ---> 费马小定理
费马小定理模板:(就是在快速幂的基础上-2)
ll pow_mod(ll a,ll b)//a的b次方对mod求余 { ll res=1; while(b) { if(b&1) res=(res*a)%mod; a=(a*a)%mod; b>>=1; } return res; } ll Fermat(ll a,ll p) //费马小定理求a关于b的逆元 { return pow_mod(a, p-2, p); }
AC代码:
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; typedef long long ll; const int mod=1e9+7; const int N=1e5+10; ll sum[N],ji[N],num[N]; int n,w; void init() { sum[0]=sum[1]=0; ji[0]=ji[1]=1; w=0; for(int i=2; ; i++) { num[w++]=i; sum[i]=sum[i-1]+i;//前缀和 ji[i]=ji[i-1]*i%mod;//前缀积 if(sum[i]>1e9) break; } } ll fm(ll a,ll b) { ll res=1; while(b) { if(b&1) res=res*a%mod; a=a*a%mod; b>>=1; } return res; } int main() { init(); int t; scanf("%d",&t); while(t--) { scanf("%d",&n); if (n==1) { printf("1\n"); continue; } int k=(upper_bound(sum,sum+w+2,n)-sum)-1; int yu=n-sum[k];//n-sum(2+3+...+k)处理多出来的数yu int p=lower_bound(num,num+w,k+1-yu)-num;//多出来的数给到k+1-yu ll ans=(ji[k]%mod*fm(num[p],mod-2)%mod)%mod; ans=ans*(num[p]+yu)%mod; printf("%lld\n",ans); } return 0; }