Detachment HDU - 5976(数学+费马小定理求逆元+前缀和前缀积)

题意:给定一个数,让你分成互不相等的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的连续数之和,然后剩下的部分从后往前一个一个分配。
这样分配后得到两种情况:

  1. Δx=l,因为我们分出的连续的数是2,3,4...l一共有l-1个数,那么说明l分完一圈后还剩下1个1,我没再把这个1分给最后一个,这样就得到数列
    3,4,5....l,l+2

  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.

根据我们上面分的两种情况

  1. Δx=l时,先得到连续数的乘积mul[l],l二分已经得到,然后比较分配后的序列,少了2,多了l+2,这样我们除去2,乘l+2即可,注意因为涉及取模故除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
  3. **以上思路参考别人**

    现在附上我自己的代码

#include<bits/stdc++.h>
#define ll long long
#define maxn 100050

using namespace std;

const ll mod=1e9+7;
ll x,a[maxn],mul[maxn];

void init(){
	a[1]=0;
	mul[1]=1;
	for(int i=2;i<maxn;i++){
		a[i]=a[i-1]+i;
		mul[i]=(i*mul[i-1])%mod; 
	}
}

ll quick_mul(ll a,ll b){
	ll ans=1;
	while(b){
		if(b&1){
			ans=ans*a%mod;
		}
		b>>=1;
		a=a*a%mod;
	}
	return ans%mod;
}

int main(){
	int t;
	init();
	scanf("%d",&t);
	while(t--){
		scanf("%lld",&x);
		if(x<=4)printf("%lld\n",x);
		else{
			ll temp=upper_bound(a+1,a+maxn,x)-a-1;
			ll temp2=x-a[temp];
			if(temp2==temp){
				ll ans=mul[temp]*quick_mul(2,mod-2)%mod*(temp+2)%mod;
				printf("%lld\n",ans%mod);
			}else{
				ll ans=mul[temp+1]*quick_mul(mul[temp-temp2+1],mod-2)%mod*mul[temp-temp2]%mod;
				printf("%lld\n",ans%mod); 
			} 
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值