HDU 5976 逆元+打表+莫队

题意

给一个数,拆分成不同的数,使得乘积最大。

题解

我们可以观察一下如何拆分乘积最大,可以发现从2开始均分。如果最后有剩余的元素,均分到每一个元素1个值,如果还有剩余(只可能剩余1个),分给最后一个元素。
暴力交一发,TLE。
这样的话,我们再次分析这个问题,我们发现拆分后的元素分为两部分(很明显就能看出来),于是这个问题就转化成了区间问题。给你二组区间,问这两组区间的乘积是多少。很明显,莫队可以解决这个问题。
莫队交一发,TLE。
我们再次分析这个问题,我们发现事实上莫队还不够好,我们发现1-1e9这个区间恰好能一次均分的元素个数不超过10^5个,于是我们把10^5个元素算出来,这样的话,我们在莫队转移的时候就多了很多元素可以利用。对于每个查询,我们首先将状态转移到最近的一个均分之后最大元素为当前状态最大元素的状态。然后剔除掉那些不存在的元素。不存在元素的数量是非常少的,我们可以认为剔除过程是O(1)的。这样的话,总体复杂度O(N)。

代码

#include<bits/stdc++.h>
#define LL long long
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(t) while(t)
#define MEM(a,b) memset(a,b,sizeof(a))
#define MAXN 1000010
#define BUF 25001000
#define MOD 1000000007
#define INF 0x3f3f3f3f
using namespace std;
LL mp[MAXN];
LL multi[MAXN];
struct Node {
    int id,x,alt,art,blt,brt;
    LL ans;
};
Node nodes[MAXN];

LL exgcd(LL a,LL b,LL &x,LL &y){
    if(b==0){
        x=1,y=0;
        return a;
    }
    LL ans=exgcd(b,a%b,x,y);
    LL temp=x;
    x=y;
    y=temp-(a/b)*y;
    return ans;
}

LL inv(LL a){
    LL x,y;
    LL t=exgcd(a,MOD,x,y);
    if(t!=1) return -1;
    return (x%MOD+MOD)%MOD;
}

int main() {
//    freopen("d://input.txt","r",stdin);
//    freopen("d://out1.txt","w",stdout);
    int sum=0;
    MEM(mp,0);
    UP(i,2,100010) {
        sum+=i;
        mp[i]=sum;
    }
    multi[1]=1;
    UP(i,2,100010) {
        multi[i]=(multi[i-1]*i)%MOD;
    }
    int t;
    scanf("%d",&t);
    UP(i,0,t) {
        nodes[i].id=i;
        scanf("%d",&nodes[i].x);
        if(nodes[i].x<5){
            printf("%d\n",nodes[i].x);
            continue;
        }
        int pos=upper_bound(mp,mp+50010,nodes[i].x)-mp-1;
        nodes[i].alt=nodes[i].art=nodes[i].blt=nodes[i].brt=1;
        if(nodes[i].x==mp[pos]) {
            nodes[i].alt=2,nodes[i].art=pos;
        } else if(nodes[i].x<mp[pos]+pos-1) {
            nodes[i].alt=2,nodes[i].art=pos-(nodes[i].x-mp[pos]);
            nodes[i].blt=pos+2-(nodes[i].x-mp[pos]),nodes[i].brt=pos+1;
        } else if(nodes[i].x==mp[pos]+pos-1) {
            nodes[i].blt=3,nodes[i].brt=pos+1;
        } else if(nodes[i].x==mp[pos]+pos) {
            nodes[i].alt=3,nodes[i].art=pos;
            nodes[i].blt=pos+2,nodes[i].brt=pos+2;
        }
//        cout<<nodes[i].alt<<" "<<nodes[i].art<<" "<<nodes[i].blt<<" "<<nodes[i].brt<<endl;
        LL ans=multi[max(nodes[i].art,nodes[i].brt)];
//        cout<<ans<<endl;
        DOWN(j,nodes[i].blt,nodes[i].art+1) ans=(ans*inv(j))%MOD;
        DOWN(j,nodes[i].alt,2) ans=(ans*inv(j))%MOD;
        printf("%I64d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值