CF938E Max History
Link
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);
}