[JXOI2018]排序问题 二分

题面
考虑一个长度为 n n 的序列Gobo Sort之后有序的期望尝试次数是

n!a1!a2!...

其中 ai a i 表示 i i 在序列中出现的次数。
那么就是我们可以把m分配给 al,..,ar a l , . . , a r ,使得 ri=lai! ∏ i = l r a i ! 尽可能小。
注意到一个性质,假设 xy x ≤ y ,那么 x!y!>(x1)!(y+1)! x ! y ! > ( x − 1 ) ! ( y + 1 ) ! ,所以大概就是要让 al,...,ar a l , . . . , a r 尽量平均。
只要二分 min(al,...,ar) min ( a l , . . . , a r ) 然后判断 m m 是否够分配即可。
复杂度O(m+nlog(n+m))
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200010
#define M 10200010
#define ll long long
using namespace std;
const int mod=998244353;
int n,m,sz,L,R,a[N],z[N],cnt[N],lp,rp;
ll fac[M];
ll ksm(ll a,int b){ll r=1;for(;b;b>>=1){if(b&1)r=r*a%mod;a=a*a%mod;}return r;}
int read()
{
    int x=0,f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1; 
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
ll check(ll x)
{
    ll tot=0;
    tot=x*(R-L-(rp-lp));
    if(tot>m) return tot;
    for(int i=lp;i<=rp&&tot<=m;i++)
        tot+=max(x-cnt[i],0ll);
    return tot; 
}
int main()
{
    fac[0]=1;
    for(int i=1;i<=M-10;i++)
        fac[i]=fac[i-1]*i%mod;  
    int ca=read();
    while(ca--)
    {
        for(int i=1;i<=n;i++)
            cnt[i]=0;   
        n=read();m=read();L=read();R=read();
        for(int i=1;i<=n;i++)
            a[i]=read();
        sort(a+1,a+n+1);
        for(int i=1;i<=n;i++)
            z[i]=a[i];
        sz=unique(z+1,z+n+1)-z-1;
        for(int i=1;i<=n;i++)
            a[i]=lower_bound(z+1,z+sz+1,a[i])-z;    
        lp=lower_bound(z+1,z+sz+1,L)-z;
        rp=upper_bound(z+1,z+sz+1,R)-z-1;
        for(int i=1;i<=n;i++)
            cnt[a[i]]++;
        ll lx=0,rx=m+n;
        while(lx<rx)
        {
            ll mid=(lx+rx)>>1;
            if(check(mid)<=m) lx=mid+1;
            else rx=mid;
        }
        ll ans=ksm(fac[lx-1],R-L-(rp-lp));
        for(int i=1;i<=sz;i++)
        {
            if(i>=lp&&i<=rp) ans=ans*fac[max((ll)cnt[i],lx-1)]%mod;
            else ans=ans*fac[cnt[i]]%mod;
        }
        ans=ans*ksm(lx,m-check(lx-1))%mod;
        ans=fac[n+m]*ksm(ans,mod-2)%mod;
        printf("%lld\n",ans);   
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值