[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);
    }
}
posted @ 2019-05-23 09:49  整理者  阅读(261)  评论(0编辑  收藏  举报