1. 程式人生 > >5976(數學+費馬小定理求逆元+字首和字首積)

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

附上程式碼:


#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;
}