hdu 5976(数论、前缀和、前缀积、逆元)

/*
    hdu 5976
    题目大意是
    给你一个数x
    将它分解成任意个数之和的形式,但这些数不能相同
    即x= a1+a2+……+an(ai!=aj)
    然后将分解的数相乘使它最大输出这个数
    即使s=a1*a2*……*an最大,输出s


    解题思路
    一个>=5的数分解成若干个数之和
    我们要乘积最大
    那么我们把x尽可能的拆分
    所以全部拆成2和3最好
    但是题目要求拆成的数不能相同
    所以我们要尽量拆成2+3+4+……+l
    当拆分成上述式子之和
    可能会剩下k(0<=k<=l,因为当大于l之后就要加一项l+1)
    这个时候可以把k看成k个1
    然后将上式从后往前每一项依次+1
    因为如果从前面加的话就会出现重复
    当k<l-1时
    肯定有些项没法+1
    在没法+1的第一项之前的都已经+1
    所以肯定会出现这种情况ai-aj==2
    也就是说会缺失一项s=l-k+1
    所以我们要求的就是(l+1)!/s%mod即可
    当k==l-1时
    刚好所有的都已经+1
    所以我们要求的就是(l+1)!/2%mod即可
    当k==l时
    所有的都已经+1
    且第一项即原来的l项加了2
    而且缺失了2和l+1这两项
    所以我们要求的就是(l+2)!/(2*l+2)%mod即可


    所以根据上述思路
    先求没有1的前缀和sum[]和前缀积mul[]
    前缀和用来判断找到的l满不满足要求
    前缀积用来求阶乘
    之后我们现在要做的就是找到l
    因为k=x-sum[l]
    然后用二分找到l
    最后按照上述思路找到缺项
    求出缺项的逆元与mul[l]相乘即可


    最后不要忘了上述思路是针对x>=5的情况
    当0<x<5时直接输出x即可
*/
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <math.h>
#include <string.h>
#define MAX 3
#define mod 1000000007
#define ll long long
using namespace std;
ll sum[50005];
ll mul[50005];
void init()//求前缀和前缀积
{
    sum[1]=0;
    mul[1]=1;
    for(int i=2;i<=50000;i++)
    {
        sum[i]=sum[i-1]+i;
        mul[i]=(mul[i-1]*i)%mod;
    }
}
void Ex_gcd(int a, int b, int &x, int &y)
{
    if(b == 0)
    {
        x = 1;
        y = 0;
        return;
    }
    int x1, y1;
    Ex_gcd(b, a%b, x1, y1);
    x = y1;
    y = x1-(a/b)*y1;
}


int main()
{
    init();
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        scanf("%d",&n);
        if(n<5)//特判
        {
            printf("%d\n",n);
            continue;
        }
        int l=0,r=50000,mid;
        while(r-l>1)//二分查找,可以粗略求一下50000的前缀和肯定大于10^9
        {
            mid=(l+r)/2;
            if(sum[mid]>n)
                r=mid;
            else
                l=mid;
        }
        int k=n-sum[l];
        int ans,x,y;
        if(k==l)//根据思路分三种情况
        {
            ll s=((2*l)%mod+2)%mod;
            Ex_gcd(s,mod,x,y);
            x=(x+mod)%mod;
            ans=(mul[l+2]*x)%mod;
            printf("%d\n",ans);
            continue;
        }
        if(k==l-1)
        {
            Ex_gcd(2,mod,x,y);
            x=(x+mod)%mod;
            ans=(mul[l+1]*x)%mod;
            printf("%d\n",ans);
            continue;
        }
        ll s=l-k+1;
        Ex_gcd(s,mod,x,y);
        x=(x+mod)%mod;
        ans=(mul[l+1]*x)%mod;
        printf("%d\n",ans);


    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值