锦标赛问题

CF1717D

首先,编号之间没有区别,所以我们不妨设布置比赛的时候顺序布置,并让每场比赛中编号最小的选手获胜,如下图:

image

这样的比赛包含一个美妙的性质,其实是可以猜出来的:

如果把每个人的编号都 \(-1\),变成 \(0 \sim 2^n - 1\),然后转化为二进制,那么从右到左第 \(i\) 位是 \(0\) 就表示:这个选手所在的“\(2^i\) 个选手组成的区域”中的胜者在第 \(i+1\) 轮会赢,否则会输。

比如 \(2 = (010)_2\),那么原来场上的 \(3\) 号选手在第一轮会赢,在第二轮会输,并且在第三轮中 \(3\) 号选手所在的 \(1 \sim 4\) 区域中胜者 \(1\) 号会赢。

这样就很容易操作了,发现把一个人保送到冠军的过程就是修改每一个 \(1\) 的位置,让他能赢。这样的话,只要 \(1\) 的个数不超过 \(k\),那么就可以做到。

因此题意等价于:求 \(0 \sim 2^n - 1\)\(\operatorname{popcount}(i) \le k\)\(i\) 的个数,也就等于 \(\sum \limits_{i = 0} ^ k \C_n^i\)。这个东西直接算算就好了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
const int mod = 1e9 + 7;
int qpow(int x, int k) {
    int ans = 1;
    while(k) {
        if(k & 1) ans = ans*x%mod;
        x=x*x%mod;
        k>>=1;
    }
    return ans;
}
int prod[100010], inv[100010];
int c(int n,int m){
    return prod[n]*inv[n-m]%mod*inv[m]%mod;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    
    int n, k; cin >> n >> k;
    if(k > n) k = n;
    prod[0]=inv[0]=1;
    f(i, 1, n) {
        prod[i]=prod[i-1]*i%mod;
        inv[i]=qpow(prod[i],mod-2);
    }
    int ans = 0;
    f(i,0,k){
        ans+=c(n,i);
        ans%=mod;
    }
    cout << ans << endl;
    time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}
posted @ 2022-09-03 10:47  OIer某罗  阅读(73)  评论(9编辑  收藏  举报