HDU - 5976 Detachment (逆元)

题意:给定x(x <= 1000000000),求 max(s| s = a1 + a2 +……+ an 且 a1 + a2 + …… + an = x (ai != aj, i != j))。

思路:显然要使s最大,那么n要尽可能大,因为1对乘积没有影响,所以a1从2开始,找到最大的n使a1 + a2 + …… + an <= x, 即 n * (a1 + an) <= x,可以求得n。可以设剩下的长度为k,把k平均分给这n个数,如果k不足n,那么分给最大的k个。得到新的a1,a2,a3 ak ak+1,an.

然后就可以分情况讨论了,如果得到的序列是连续的,可以直接求。如果是两段,记下这两端的端点分别为k1, k2, k3, k4.先求第一段sum1 = k2! / k1! mod (1e9 + 7)。同理第二段sum2 = k4!/k3! mod (1e9+7).

那么问题来了怎么求k2! / k1! mod (1e9 + 7),直接求不好求。这里就需要用到逆元,如果求 a / b mod p,那么它就等于a乘以b的逆,只要求出b的逆问题就解决了。这里用1/n!表示n的阶乘的逆。 1/n! = (n + 1) * 1 / (n+1)!用递推式求得所有n的阶乘的逆。

n的阶乘的逆可以用由费马小定理,a^(p-1)≡ 1(mod p)(p为素数),稍作变形即是 a * a^(p-2)≡ 1(mod p),是不是发现了,a^(p-2)即是a的逆元,这个可以用快速幂来求。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include<vector>
#include<cmath>
#include<set>
#include<cstring>
#include<map>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int maxt = 100200;
const int inf = 0x3f3f3f3f;
const ll INF = 0x7f7f7f7f7f;
const int mod = 1e9 + 7;
const double pi = acos(-1.0);
const double eps = 1e-8;
ll quick_inverse(ll ret, int n){ // 快速幂
    ll sum = 1;
    while(n){
        if(n & 1) sum = sum * ret % mod;
        ret = ret * ret % mod;
        n >>= 1;
    }
    return sum;
}
ll ni[maxn];
ll ji[maxn];
void init(){
    ll sum = 1;
    ji[0] = 1;
    for(int i = 1; i <= 100000; ++i){
        ji[i] = ji[i - 1] * i % mod;
    }//阶乘打表
    ni[100000] = quick_inverse(ji[100000], mod - 2);
    for(int i = 99999; i >= 0; --i) // 阶乘的逆打表
        ni[i] = ni[i + 1] * (i + 1) % mod;
}
void solve(int x){
    ll  tt = 9 + 8LL * x;
    ll t = sqrt(tt);
    int n = (t - 3) / 2; //由一元二次方程(n + 3) * n / 2 = x 解得
    for(int i = 1; ; ++i){ //使n最大
        if((ll)(n + 1 + 3) * (ll)(n + 1) / 2 <= x){
                n++;
        }
        else break;
    }
    ll sheng = x - (n + 3) * n / 2; 
    int k1 = 2, k2 = n + 1;
    int k3 = -1;
    while(sheng){
        if(sheng >= n){
            k1 += sheng / n;
            k2 += sheng / n;
            sheng %= n;
        }
        else{
            k2++;
            k3 = k2 - sheng;
            sheng = 0;
        }
    }
    if(n == 1) printf("%d\n", x);
    else if(k3 != -1){
        ll ans = ji[k2] * ni[k3] % mod;
        ans = (((ans * ji[k3 - 1])%mod) * ni[k1 - 1]) % mod;
        printf("%lld\n", ans);
    }
    else{
        ll ans = ji[k2] * ni[k1 - 1] % mod;
        printf("%lld\n", ans);
    }
}
int main(){
    int T, kase = 0;
    init();
    scanf("%d", &T);
    while(T--){
        int x;
        scanf("%d", &x);
        if(x == 1) printf("1\n");
        else solve(x);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值