蓝桥杯 高塔 题解

吐槽

这个题给中等难度感觉有点搞,感觉能算困难题了,蓝桥平台上其他题解都没有解释,看着像互相抄的

特别鸣谢

感谢起风了 q哥 提供的99.99%的思路

前言

本题解所用的知识点: 逆元,快速幂,生成函数,组合数
有不清楚的可以先学习了再回来,个人推荐b站up主董晓算法

问题描述

小蓝正在玩一个攀登高塔的游戏。高塔的层数是无限的,但游戏最多只有 n 回合。
小蓝一开始拥有 m 点能量,在每个回合都有一个值 \(A_i\),表示小蓝的角色状态。小蓝每回合可以选择消费任意点能量 \(C_i\)(最低消费 1 点,没有上限),他在这回合将最多可以向上攀爬 \(A_i \cdot C_i\) 层。实际攀爬的层数取决于小蓝自己在这回合的表现,不过最差也会向上爬一层。
当某回合小蓝的能量点数耗尽,那么在完成这个回合后,游戏结束。
n 回合结束后,不管能量还有没有剩余,游戏都会直接结束。

给出小蓝每回合的 \(A_i\) 和自己一开始的能量点数 \(m\)。小蓝想知道有多少种不同的可能出现的游玩过程。如果小蓝在两种游玩过程中的任一对应回合花费的能量点数不同或该回合结束时所处层数不同,那么这两种游玩过程就被视为不同。

给出小蓝每回合的 \(A_i\) 和自己一开始的能量点数 m。小蓝想知道有多少种不同的可能出现的游玩过程。如果小蓝在两种游玩过程中的任一对应回合花费的能量点数不同或该回合结束时所处层数不同,那么这两种游玩过程就被视为不同。

输入格式
输入的第一行包含两个整数 n, m,用一个空格分隔。
第二行包含 n 个整数 \(A_i\),相邻整数之间使用一个空格分隔,表示小蓝每回合的状态值。
当某回合小蓝的能量点数耗尽,那么在完成这个回合后,游戏结束。
n 回合结束后,不管能量还有没有剩余,游戏都会直接结束。

输出格式
输出一行包含一个整数表示给定条件下不同游玩过程的数量。由于答案可能很大,你需要输出答案对 998244353 取模的结果。

数据范围
1 \(\le\) n \(\le\) 2 x \(10^5\),n \(\le\) m \(\le\) \(10^{18}\),1 \(\le A_i \le 10^9\)

题解

1.思路分析

题目大意就是每一层消耗 \(C_i\) 点能量,1 \(\le C_i \le\) 当前剩余能量,然后会向上爬,1 \(\le 上升的层数 \le A_i \cdot C_i\), 因为消耗能量不同或所处层数不同视为不同方案,那么消耗\(C_i\)点能量会使方案数乘上\(A_i \cdot C_i\)倍,如果按照动态规划来做就是转移就是:dp[i][j + \(C_i\)] = dp[i - 1][j]*\(A_i \cdot C_i\),假设i为当前所处层数,j为当前消耗能量,dp[i][j]是方案数,但是这样做时间复杂度是O(n\(m^2\)),因为转移时还要枚举\(C_i\),所以不能直接dp。

2.生成函数

考虑 f(x) = \(A_i\)x + 2\(\cdot A_ix^2\) + 3\(\cdot A_ix^3\) + ....
每一项的系数为方案数增加的倍数,幂次为消耗的能量,注意幂次从1开始因为最低消费1点
如果直接f(x)相乘就相当于dp的做法,所以我们考虑变形一下,将\(A_i\)提出来:
得到 f(x) = \(A_i\)(x + 2\(\cdot x^2\) + 3\(\cdot x^3\) + ....)
令h(x) = x + 2\(\cdot x^2\) + 3\(\cdot x^3\) + ....
这个可以直接搜到公式,但也可以推导一下:
首先geometric series:
\(\frac{1}{1-x} = 1 + x + x^2 + x^3 + ....\)
我在这里证明一下,因为这是我第一次做生成函数的题哈哈哈
\(1 + x + x^2 + x^3 + .... = \lim_{n \to \infty}(\frac{1 - x^n}{1 - x}) = \frac{1}{1 - x} + \lim_{n \to \infty}(\frac{-x^n}{1 - x})\),注意x的范围是\((-1,1)\),我的理解是因为x的取值并不重要,取这个区间可以让函数具有这些特殊性质,所以\(\lim_{n \to \infty}(\frac{-x^n}{1 - x}) = 0\),所以\(\frac{1}{1-x} = 1 + x + x^2 + x^3 + ....\)
其次有两种方法堆出h(x)的对应函数:

  1. q哥做法
    \(g(x) = \frac{x}{1 - x} = x + x^2 + x^3 ...\)
    \(x \cdot h(x) = x^2 + 2\cdot x^3 + 3\cdot x^4 ...\)
    注意到:
    $ g(x) + x\cdot h(x) = h(x)$ 所以 \(h(x) = \frac{x}{(1 - x)^2}\)
    2.对geometric series求导
    \(\frac{1}{(1 - x)^2} = 1 + 2\cdot x + 3\cdot x^2 + 4\cdot x^3 + ....\)
    乘上x得到 \(\frac{x}{(1 - x)^2} = x + 2\cdot x^2 + 3\cdot x^3 + 4\cdot x^4 + ....\)

3.找出答案

根据以上,我们可以知道对于第 i 轮,函数为\((\prod_{j = 1}^{i}A_j) \cdot \frac{x^i}{(1 - x)^{2 \cdot i}}\),将\(A_j\)提取出来,这部分可以预处理pre[j]表示前j轮\(A_j\)的乘积。
对于前n - 1轮,我们需要的是幂次为m项的系数,而对于最后一轮我们需要所有幂次为 \(\le m\)项的系数,因为提前终止需要消耗万能量,而最后一轮不论剩多少都停止。
前n - 1轮:
怎么求系数呢,这就要再转换回power series,数学公式再次来袭!
推导
核心是多次求导:
考虑对\(\frac{1}{1 - x}\)求导n次后的结果:
1次:
\(\frac{1}{(1 - x)^2} = 1 + 2\cdot x + 3\cdot x^2 + 4\cdot x^3 + ....\)
2次:
\(\frac{2}{(1 - x)^3} = 2 + 3 \cdot 2 x + 4 \cdot 3 x^2 + 5 \cdot 4 x^3 + ....\)
...
n - 1次: (n - 1是因为我们需要分母是n次,方便一点)
\(\frac{(n - 1)!}{(1 - x)^n} = \sum_{r = 0}^{\infty} x^r \cdot \frac{(r + n - 1)!}{r!}\),考虑\(x^r\)是从最开始的\(x^k\)变过来的:
r + n - 1 = k,因为求了n - 1次导
系数为\(k \cdot (r + n - 1) \cdot (r + n - 2) \cdot ... \cdot (r + 1)\),即\(\frac{(r + n - 1)!}{r!}\)
注意到有(n - 1)!,(r + n - 1)!,r!这显眼的几项,立刻想到组合数,于是将(n - 1)!除到右边,
得到\(\frac{1}{(1 - x)^n} = \sum_{r = 0}^{\infty} x^r \cdot \binom {r + n - 1}{n - 1}\)
于是\(\frac{x^i}{(1 - x)^{2 \cdot i}}\) = $ \sum_{r = 0}^{\infty} x^{r + i} \binom {r + 2\cdot i - 1}{2\cdot i - 1}$

因为前n - 1轮的话,每一轮我们要找的是幂次为m,因此令r + i = m,于是第i轮的答案即\((\prod_{j = 1}^{i}A_j)\binom {m + i - 1}{2\cdot i - 1}\),此处将r + i用m替换掉了,但是由于m,n都很大,这里需要预处理:
考虑\(\binom {m + i - 1}{2\cdot i - 1} = \frac{(m + i - 1)!}{(2\cdot i - 1)! \cdot (m - i)!}\),注意到,m + i - 1的范围:\([m,m + n - 1]\),2\(\cdot i - 1\)的范围:\([1,2n - 1]\),m - i的范围:\([m - n,m - 1]\),发现我们可以分别预处理\([1,2 \cdot n],[m - n,m + n]\)的阶乘,两个的大小都是O(n)级别的,由于第二个阶乘不是从1开始的,我是用umap来存的。
还要注意的是由于有取模,需要求一下两个分母的逆元,第一个范围较小可以O(n)线性求逆元预处理,但是没必要,因为第二个没办法线性递推,需要快速幂来求,但我懒得改代码了,所以我的代码会有两个逆元。
于是每一轮O(logn)
最后一轮
是不是以为做完了,并没有,最后一轮不同于前面,不仅需要幂次为m,而是需要\([n,m]\),小于n不考虑是因为每轮至少消耗1点能量。
还是回到$ \sum_{r = 0}^{\infty} x^{r + i} \binom {r + 2\cdot i - 1}{2\cdot i - 1}$,此时i为n,因为i是轮数,令k = r + n,

所以变化为\(\sum_{r = 0}^{\infty} x^{k} \binom {k + n - 1}{2\cdot n - 1}\),第n轮的答案即为\(\prod_{j = 1}^{n} A_j \cdot (\sum_{k = n}^{m} \binom {k + n - 1}{2\cdot n - 1})\),因为需要的是对于 \(k \in [n,m]\)的系数和,但是这个却无法预处理了,因k + n - 1 \(\in [2\cdot n - 1,n + m - 1]\),于是我们走头无路,只能寻求数学方法,因为这个式子只有k在每次加一,于是发现这个式子等于 \(\binom {m + n}{2\cdot n}\),哇噻,amazing啊!
推导
数学归纳法
\(\binom {r}{r} + \binom {r + 1}{r} + ... \binom {n}{r} = \binom {n + 1}{r + 1} \textcircled{1}\)
注意到:
\(\binom {n}{r} + \binom {n}{r + 1} \textcircled{2} = \binom {n + 1}{r + 1}\)
\(\textcircled{1} - \textcircled{2}\) 得到 \(\binom {r}{r} + \binom {r + 1}{r} + ... \binom {n - 1}{r} = \binom {n}{r + 1} \textcircled{3}\)
base case:
\(\binom {r}{r} = \binom {r + 1}{r + 1} = 1\),于是根据 \(\textcircled{3}\),即可从下向上进行induction,就不详细写了
最后:\(\binom {m + n}{2\cdot n}\) = \(\frac{(m + n)!}{(2\cdot n)! \cdot (m - n)!}\),由于m - n \(\in [0,m - n]\),发现刚好这些都在前面处理出的范围之内,所以直接算就好了。

最后
n - 1轮加上最后一轮的答案,所有过程种都要不停的取模,避免溢出。我做的时候非常傻逼得将N设为998244353,模数设为1e9 + 7算了一天一直样例都算不出来,希望没人和我一样犯这个错,这是我第一次写题解,可能会有错误得地方,欢迎指出,我也会及时回复更正。

代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
#define endl '\n'
const int mod = 998244353;
const int N = 5e5 + 10;
ll inv[N];
unordered_map<ll,ll> pre2;
void init() // initialize the inv prefix
{
    inv[1] = 1;
    for(int i = 2;i < N;i++) inv[i] = (mod - mod / i) * inv[mod % i] %mod;
    for(int i = 2;i < N;i++) inv[i] = inv[i - 1] * inv[i] % mod;
}
ll qpow(ll a,ll b) // fast exponetiation
{
    ll res = 1;
    while(b)
    {
        if(b&1) res = res*a%mod;
        a = a*a%mod;
        b >>= 1;
    }
    return res;
}
ll INV(ll x) { return qpow(x,mod - 2);}
ll C(ll n,ll m) // combination 
{
    if(n < m) return 0;
    else if(n == m) return 1;
    ll res = 1;
    res = pre2[n]*INV(pre2[n - m])%mod*inv[m]%mod;
    return res%mod;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    init();
    ll n,m; cin>>n>>m;
    vector<ll> a(n),pre(n + 1);
    pre[0] = 1;
    for(int i = 0;i < n;i++) 
    {
        cin>>a[i];
        pre[i + 1] = (pre[i] * a[i]) % mod;
    }
    pre2[m - n] = (m - n)%mod;
    for(ll x = m - n + 1;x <= m + n;x++)
        pre2[x] = pre2[x - 1]*(x%mod)%mod;
    ll res = 0;
    for(int i = 0;i < n - 1;i++) 
    {
        ll cur = C(m + i,2*i + 1)*pre[i + 1]%mod; // use m points at ith round,i < n
        res = (res + cur)%mod;
    }
    res = (res + C(m + n,2*n)*pre[n]%mod)%mod; // ith round
    cout<<(res + mod)%mod;
    return 0;
}
posted on 2025-01-16 07:07  大便_超人  阅读(660)  评论(0)    收藏  举报