寒假Day54:HDU5976Detachment前缀和+前缀积+逆元(费马小定理)+二分

题目链接:

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;
}
原文地址:https://www.cnblogs.com/OFSHK/p/12513938.html