A. 困难数学题

题意

求 n xor n xor n xor n

大概思路

求 n xor n xor n xor n

参考代码

求 n xor n xor n xor n

void solve(){
    int n;
    std::cin >> n;
    std::cout << (n ^ n ^ n ^ n);
}

其实当时想到了可以直接输出0,但是为了保险一点就按照题目说的模拟了

B. 构造序列

题意

给你 nn 个正数和 mm 个负数,请你使用这些数字,构造一个序列。

序列需要满足:正数不能和正数相邻,负数不能和负数相邻。

那么,最多能构造多长的序列?

大概思路

贪心,选择较少的,乘2,然后判断较多的能不能补一个

参考代码

void solve(){
    int n, m;
    std::cin >> n >> m;
    std::cout << 2 * std::min(n, m) + (n != m);
}

C. 连点成线

题意

小红有一个 n×n 大小的格子棋盘,我们使用 (i,j) 表示棋盘中从上往下数第 i 行和从左往右数第 j 列的单元格。棋盘上有 m 个棋子,第 i 个棋子的坐标是 (xi,yi) 。

对于一对棋子 i 和 j ,如果满足 xi​=xj​ 或者 yi​=yj​ ,则两个棋子之间可以连线。

小红有点无聊,于是她把能连的线都连上了。现在,她想知道,最长的那条线,长度是多少。

特别地,如果连线数量为 0 ,则输出 0 。

大概思路

暴力map,统计每行最左,每行最右,每列最上面,每列最下面的,然后动态更新答案即可

参考代码

void solve(){
    int n, m;
    std::cin >> n >> m;
    
    std::map<int, int> L, R, U, D;
    int ans = 0;
    while (m--) {
        int x, y;
        std::cin >> x >> y;
        if (L.count(y)) {
            L[y] = std::min(L[y], x);
            R[y] = std::max(R[y], x);
        } else {
            L[y] = R[y] = x;
        }
        if (U.count(x)) {
            D[x] = std::min(D[x], y);
            U[x] = std::max(U[x], y);
        } else {
            D[x] = U[x] = y;
        }
        ans = std::max(ans, R[y] - L[y]);
        ans = std::max(ans, U[x] - D[x]);
    }
    std::cout << ans;
}

D. 我们N个真是太厉害了

题意

给你 n 个数,问你对于1 到 n任意一个数i,是否都存在一个子集和为i。

大概思路

sort一下,从小到大一个个更新MEX,如果当前的数a[i]大于MEX,那么没法再更新了(因为MEX覆盖不到),直接输出MEX;否则更新MEX为MEX + a[i]。

参考代码

void solve(){
    int n;
    std::cin >> n;
    
    std::vector<int> a(n);
    for (auto &i : a) std::cin >> i;
    
    std::sort(a.begin(), a.end());
    
    int MEX = 1;
    for (auto &i : a) {
        if (i > MEX) {
            std::cout << MEX << "\n";
            return;
        } else {
            MEX = std::min(n + 1, MEX + i);
        }
        
        if (MEX == n + 1) {
            std::cout << "Cool!\n";
            return;
        }
    }
    std::cout << MEX << "\n";
}

E. 折返跑

题意

笔直的跑道可以看作是一条数轴。跑道上有 n 个点位,其中,老师在 1 点和 n 点处各放了两根杆子,称为左杆和右杆,作为折返跑的折返点。

老师规定,今天一共需要跑 m 趟。我们认为,从一根杆子开始,跑到另一根杆子结束为一趟,显然,一共需要进行 m−1 次折返。Zaoly偶然发现,杆子是可推动的,所以他有了一个大胆的想法——

  • 每次跑至右杆折返后,都需要把右杆向左推动一段距离(大于 0),但仍保证右杆位于整数点,且在左杆的右边(不能与左杆重合);

  • 每次跑至左杆折返后,都需要把左杆向右推动一段距离(大于 0),但仍保证左杆位于整数点,且在右杆的左边(不能与右杆重合);

现在,给出整数 n 和 m 的值,请问Zaoly一共有多少种不同的推杆方法?由于答案可能很大,请将答案对 $(10^9+7)$ 取模后输出。注意,每次折返时都需要推杆,如果某一轮无法推动,则这一轮推杆非法。

大概思路

纸上分析一下样例 “4 3” 和 “5 3”,很容易发现答案就是 $C_{n - 2}^{m - 1}$。

参考代码

constexpr int M = 1e9 + 7;
i64 powp(i64 a, i64 n) {
    i64 ans = 1;
    while (n) {
        if (n & 1) (ans *= a) %= M;
        (a *= a) %= M;
        n >>= 1;
    }
    return ans;
}

i64 inv(i64 x) {
    return powp(x, M - 2);
}

constexpr int N = 1e6 + 1;

i64 fac[N], invfac[N];
 
void pre() {
    fac[0] = invfac[0] = 1;
    for (int i = 1; i < N; i++) fac[i] = fac[i-1] * i % M;
    invfac[N - 1] = inv(fac[N - 1]);
    for (int i = N - 2; i >= 1; i--) invfac[i] = invfac[i + 1] * (i + 1) % M;
}
 
i64 C(i64 n,i64 m) {
    if (m > n) return 0;
    return fac[n] * invfac[n - m] % M * invfac[m] % M;
}

void solve(){
    int n, m;
    std::cin >> n >> m;
    
    std::cout << C(n - 1 - 1, m - 1) << "\n";
}

F. 口吃

题意

Zaoly要讲一句话,这句话有 n 个字,他要一个字一个字讲出来。奈何 Zaoly 口吃:

  • 讲到第 1 个字时,下一个要讲的字有 $\frac{​a_1}{a_1​+b_1}$​​ 的概率前进到第 2 个字,有 $\frac{b_1}{a_1​+b_1}$​ 的概率仍是第 1 个字。
  • 讲到第 i(2≤i≤n−1)个字时,下一个要讲的字有 $\frac{a_i^2}{a_i^2 + 2a_ib_i + b_i^2}$​​ 的概率前进到第 i+1 个字,有 $\frac{2a_ib_i}{a_i^2 + 2a_ib_i + b_i^2}$ 的概率仍是第 i 个字,有 $\frac{b_i^2}{a_i^2 + 2a_ib_i + b_i^2}$​​ 的概率倒退到第 i−1 个字。
  • 讲到第 n 个字时,讲话完毕,停止讲话。

直到讲话完毕,Zaoly总共讲出的字数的期望是多少?

大概思路

一眼带环的期望dp(为什么带环?因为可以从后面的状态转移到之前的状态),所以不能用普通的期望dp一直往后推,而是要用高斯消元的方法。

注意到第一项只有一个自环和向前推进。列出转移方程:

$$ E[1] = \frac{a_1}{a_1 + b_1}E[2] + \frac{b_1}{a_1 + b_1}E[1] + 1$$

移项整理有:

$$ E[1] = E[2] + \frac{a_1 + b_1}{a_1}$$

设 $ E[i] = P[i]E[i + 1] + Q[i] $,则 $P[1] = 1$,$Q[1] = \frac{a_1 + b_1}{a_1}$

对 $i (i \ge 2)$ 有如下转移方程:

$$ E[i] = \frac{a_i^2}{a_i^2 + 2a_ib_i + b_i^2}E[i + 1] + \frac{2a_ib_i}{a_i^2 + 2a_ib_i + b_i^2}E[i] + \frac{b_i^2}{a_i^2 + 2a_ib_i + b_i^2}E[i - 1] + 1 $$

将 $ E[i] = P[i]E[i + 1] + Q[i] $ 代入到上式的 $E[i - 1]$ 中并移项整理有:

$$ E[i] = \frac{a_i^2}{a_i^2 + b_i^2 - b_i^2P[i - 1]}E[i + 1] + \frac{b_i^2Q[i - 1] + (a_i + b_i)^2}{a_i^2 + b_i^2 - b_i^2P[i - 1]} $$

也就是说:

$$ P[i] = \frac{a_i^2}{a_i^2 + b_i^2 - b_i^2P[i - 1]} $$ $$ Q[i] = \frac{b_i^2Q[i - 1] + (a_i + b_i)^2}{a_i^2 + b_i^2 - b_i^2P[i - 1]} $$

其和 $E[i]$ 无关,那么我们可以预处理出所有的 $P[i]$, $Q[i]$,再用 $E[i] = P[i]E[i + 1] + Q[i] $倒序推出 $E[1]$ 即可,易知 $E[n] = 1$

参考代码

constexpr int M = 1e9 + 7;
i64 powp(i64 a, i64 n) {
    i64 ans = 1;
    while (n) {
        if (n & 1) (ans *= a) %= M;
        (a *= a) %= M;
        n >>= 1;
    }
    return ans;
}

i64 inv(i64 x) {
    return powp(x, M - 2);
}

void solve(){
    int n;
    std::cin >> n;
    std::vector<i64> a(n), b = a;
    
    for (int i = 1; i < n; i++) std::cin >> a[i];
    for (int i = 1; i < n; i++) std::cin >> b[i];
    
    std::vector<i64> p(n), q = p;
    
    p[1] = 1; q[1] = (a[1] + b[1]) * inv(a[1]) % M;
    for (int i = 2; i < n; i++) {
        i64 fm = (a[i] * a[i] % M + b[i] * b[i] % M - b[i] * b[i] % M * p[i - 1] % M + M) % M;
        fm = inv(fm);
        p[i] = (a[i] * a[i] % M) * fm % M;
        q[i] = (b[i] * b[i] % M * q[i - 1] % M + ((a[i] + b[i]) % M) * ((a[i] + b[i]) % M) % M) % M * fm % M;
    }
    
    std::vector<i64> E(n + 1);
    E[n] = 1;
    
    for (int i = n - 1; i >= 1; i--) {
        E[i] = (p[i] * E[i + 1] % M + q[i]) % M;
    }
    
    std::cout << E[1];
}