CF938E Max History

Solution

考虑每个元素的贡献。该元素对答案有贡献当且仅当前面没有比该元素大的数,且后面存在比该元素大的数。容易想到先将序列排序。

假设所有元素都不同,当前是第 \(i\) 个,枚举该元素前面的数的个数,然后计数

\[\sum_{k=0}^{i-1} k!(n-k-1)!\binom{i-1}{k} \]

然后就会发现 \(k!(n-k-1)!\) 是个常数列,记为 \(f_k\)。然后就会发现上面的式子是 \(f\)\(1\) 的 egf 的二项卷积。NTT 一下就做完了。(但是模数是 1e9+7,这种方法并不可取)

还是太年轻了。

由于 \(f_k\) 这个东西很特殊,所以应该先考虑化简。注意到有

\[k!(n-k-1)!=\frac{k!(n-k-1)!(n-1)!}{(n-1)!}=(n-1)!\binom{n-1}{k}^{-1} \]

所以就化成

\[(n-1)! \sum_{i=0}^{i-1} \binom{i-1}{k}\Big/\binom{n-1}{k} \]

然后你就会发现这个和《具体数学》5.2 第一个例题长得差不多,大概就是考虑运用三项恒等式后再上指标求和。

最后便是

\[(i-1)!(n-i)!\binom{n}{i-1} \]

然后再看有重复元素的。容易发现必须是第一个该值的元素才会造成贡献,所以只需要把所有同值元素按照第一个遇到的该种元素处理即可。

update 2023.10.1

考虑答案的组合意义。当前是第 \(i\) 个数,将前面的数的全排列分配到序列中,有 \(\binom{n}{i-1}(i-1)!\) 种,然后钦定第一个空位放第 \(i\) 个数,剩下的空位用剩下的数填满即可,有 \((n-i)!\) 种。

#include<stdio.h>
#include<algorithm>
using namespace std;

inline int read(){
    int x=0,flag=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') flag=0;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
    return flag? x:-x;
}

typedef long long ll;

const int N=1e6+7;
const int Mod=1e9+7;

int qpow(ll x,int y=Mod-2){
    ll ret=1;
    while(y){
        if(y&1) ret=ret*x%Mod;
        x=x*x%Mod,y>>=1; 
    }
    return ret;
} 

ll fac[N],a[N],inv[N];

ll C(int x,int y){return x<y? 0:fac[x]*inv[y]%Mod*inv[x-y]%Mod;}

int main(){
    int n=read(); fac[0]=1;
    for(int i=1;i<=n;i++)
        a[i]=read(),fac[i]=fac[i-1]*i%Mod;
    inv[n]=qpow(fac[n]);
    for(int i=n-1;~i;i--)
        inv[i]=inv[i+1]*(i+1)%Mod;
    sort(a+1,a+1+n); int mx=a[n];
    ll ans=0,q=0;
    for(int i=1;i<=n;i++){
        if(a[i]==mx) break;
        if(a[i]!=a[i-1]) q=fac[i-1]*fac[n-i]%Mod*C(n,i-1)%Mod;
        ans=(ans+a[i]*q%Mod)%Mod;
    }
    printf("%lld",ans);
}
posted @ 2021-07-13 21:55  Kreap  阅读(53)  评论(3编辑  收藏  举报