[PKUSC2018]真实排名
组合数学
对每个数,先分类讨论,将答案分为这个数有翻倍和这个数没翻倍。
在讨论前,我们规定:\(low(x)\)为小于等于这个数的数的个数,当前数为\(x\)。
subtask1
如果这个数没翻倍:我们考虑哪些数翻倍不会影响这个数的排名,一种是翻倍后依然小于\(x\)的,一种是本来就大于等于\(x\)的。
那么对于第一种情况,情况数为
\[C(low((x+1)/2-1)+n-low(x-1)-1,k)
\]
(\(C(n,m)\)为组合数,下同)
这里前面一项最后的减一表示不能选当前数。
subtask2
如果这个数翻倍了:我们考虑那些数必须翻倍,那些数可以选择性翻倍。
必须翻倍的数就是为了维持原排名所需翻倍的数,就是小于\(2x\)同时大于等于\(x\)的所有数。
选择翻倍的数就是翻倍后不会影响当前排名的数,就是小于\(x\)的数和大于等于\(2x\)的数的数。
这里要注意,如果必须翻倍的数大于\(k\)那么在这种讨论中就不可能有情况满足当前数排名不变。
设必须翻倍的数为\(js=low(2x-1)-low(x-1)\),那么对于第二种情况,情况数为
\[C(n-js,k-js)
\]
那么答案就是两者相加了。
#include<bits/stdc++.h>
using namespace std;
const int N=100010,p=998244353;
int add(int x,int y){
return (x+y)%p;
}
int mul(int x,int y){
return 1ll*x*y%p;
}
int n,k;
int a[N],b[N];
int fac[N],inv[N];
int C(int n,int m){
return mul(fac[n],mul(inv[m],inv[n-m]));
}
int mpow(int a,int n){
int ret=1;
while(n){
if(n&1)ret=mul(ret,a);
a=mul(a,a);
n/=2;
}
return ret;
}
int low(int x){
return (upper_bound(b,b+n+1,x)-b-1);
}
int main(){
inv[0]=fac[0]=1;
for(int i=1;i<N;++i)fac[i]=mul(fac[i-1],i);
inv[N-1]=mpow(fac[N-1],p-2);
for(int i=N-2;i;--i)inv[i]=mul(inv[i+1],i+1);
cin>>n>>k;
for(int i=1;i<=n;++i){
scanf("%d",a+i);
b[i]=a[i];
}
sort(b+1,b+n+1);
b[0]=-1e9+7;
b[n+1]=0x7fffffff;
for(int i=1;i<=n;++i){
if(a[i]==0){
printf("%d\n",C(n,k));
continue;
}
int ret=C(low((a[i]+1)/2-1)+n-low(a[i]-1)-1,k);
int js=low(2*a[i]-1)-low(a[i]-1);
if(k>=js){
ret=add(ret,C(n-js,k-js));
}
printf("%d\n",ret);
}
}