[小菜一碟]4.11测试讲解
#0.0 前言
这里是本人出的校内测试题水题的题解,题面见此:【氵题】,Password:0411
(T3 不是我出的,所以我不写 T3 题解(-、-))
#1.0 悠闲的小Aik
#1.1 理解题意
首先,不得不承认,因为本人的语文水平较低,导致题面有些难懂,我们来看几个问题
- \(c_i\) 到底是干什么用的?到底是什么意义?
\(c_i\) 可以这么理解:对于关卡 \(i\),若 \(\exists k\in[i-c_i,i-1]\),第 \(k\) 关已被通过,那么可以直接挑战第 \(i\) 关
- 来看下面这个数学式子:
其实 \(7^{362}\) 和 \(49^4\) 这两个数是我直接从选修4-6上抄下来的,运用费马小定理易知:
故有
由此我们知道了,在任意时刻,\(M\) 总能被 \(100\) 整除,再结合数据范围 \(M\leq10^4\),可以看出,\(M\) 的实际取值只有 \(101\) 个数(\(0\times100\sim100\times100\))
#1.2 设计状态,写出方程
我们可以这样设计状态:
\(f_{i,j}\) 表示到达第 \(i\) 关时能力值为 \(j\),在该状态下通过这一关后的最小步数
显然有转移方程:
来理解一下上面的柿子,我们的转移候选区间为 \([i-c_i,i-1]\),我们需要一个最小的 \(f_{k,j-b_k}\),在此基础上进行转移,即步数加一。那么为什么是 \(j-b_k\)?回想我们刚才设计的状态:第二维的 \(j\) 是到达时的能力值而非通过后,所以我们需要减去上一关的收益。因为在任意时刻 \(M\) 都可以被 \(100\) 整除,所以我们直接在输入后处理即可。
#1.3 伪代码
\(j\) 的范围很小,可以直接枚举,时间复杂度 \(O(100\cdot n^2)\)
#1.4 优化
很显然,上面程序的时间复杂度使我们不能接受的,考虑优化。
观察上面的状态转移方程:
我们想要的仅是区间 \([i-c_i,i-1]\) 的最小值,当 \(i\) 变为 \(i+1\) 时,方程变为:
题干中又给出 “\(c_i\) 非严格递减”,这也就意味着,对于同一个 \(j\) 时,\(f_{i,j}\) 与 \(f_{i+1,j}\) 的候选区间有很大的交叉,区间左端点会向右平移几位,右端点向右平移一位。这样的特点不能不让我们想起 单调队列,但要是 \(j\) 变化呢?
候选区间已经完全不一样了,但是,\(j\) 的范围很小,我们为什么不能多开几个单调队列?于是,我们可以对于每一个 \(j\),单独看一个单调队列。
几个注意的点:
- 由于我们设计的状态,在更新完 \(f_{i,j}\) 后,我们应当将其加入 \(j'=j+b_i\) 的候选区间,也就是 \(j+b_i\) 这条单调队列
- 在维护单调性时,我们应将 \(f_{i,j}\) 与队列尾部的 \(f_{k,j+b_i-b_k}\) 进行比较,请结合上一条进行理解。
const int N = 50010;
const int INF = 0x3fffffff;
struct Hurdle{
int a;
int b;
int c;
};
Hurdle h[N];
int n,x,f[N][201],q[201][N];
int frt[201],tal[201];
int main(){
mset(tal,-1); //memset(tal,-1,sizeof(tal));
mset(f,0x3f);
scanf("%d%d",&n,&x);
x /= 100;
for (int i = 1;i <= n;i ++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
h[i].a = a / 100;
h[i].b = b / 100;
h[i].c = c;
}
f[1][x] = 1;
q[h[1].b + x][++ tal[h[1].b + x]] = 1;
for (int i = 2;i <= n;i++){
for (int j = 100;j >= h[i].a;j --){
int nxt = j + h[i].b;
while (frt[j] <= tal[j] && q[j][frt[j]] < i - h[i].c)
frt[j] ++;
if (frt[j] <= tal[j])
f[i][j] = f[q[j][frt[j]]][j - h[q[j][frt[j]]].b] + 1;
while (frt[nxt] <= tal[nxt] && f[q[nxt][tal[nxt]]][nxt - h[q[nxt][tal[nxt]]].b] >= f[i][j])
tal[nxt] --;
q[nxt][++ tal[nxt]] = i;
}
}
int minn = INF;
for (int i = 0;i <= 100;i ++)
minn = min(minn,f[n][i]);
if (minn > n) cout << "+200";
else cout << minn;
return 0;
}
#2.0 卡牌大师
#2.1 简化题目
至多 \(m\) 个 \(A\) 与至多 \(n\) 个 \(B\) 可形成的排列总数(不计空排列)
#2.2 颓柿子
依次考虑 \(A\) 的个数与 \(B\) 的个数,可知答案为
直接循环处理这个柿子的时间复杂度为 \(O(mn)\),显然不行,要颓柿子
我们知道,有 朱世杰恒等式[1]
我们的柿子正好符合这个形式
好的,现在我们得到了一个看似简单了许多的柿子,但是注意到 \(n,m\leq10^{18}\),虽然有模数 \(19260817\),但仍旧无法得到我们想要的结果,如果要预处理,\(n,m\) 太大了,如果要直接用 \(\tbinom{n}{m}=\tfrac{n!}{m!(n-m)!}\),显然 \(n,m\) 依旧太大,所以要使用另一个定理:Lucas 定理[2]
设 \(p\) 是一个质数,\(n\) 用 \(p\) 进制表示为 \(a_0+a_1p^1+a_2p^2+\cdots+a_kp^k\),\(m\) 用 \(p\) 进制表示为 \(b_0+b_1p^1+b_2p^2+\cdots+b_kp^k\),那么有:
所以,我们就可以将 \(n+m+2\) 和 \(n+1\) 分别转为 \(p\) 进制,再运用卢卡斯定理即可。
我们再次注意到,如果要递推求 \(\tbinom{a_i}{b_i}\),最大要预处理到 \(\tbinom{p}{p}\),时间、空间复杂度都为 \(O(p^2)\),不能接受,所以我们可以线性求阶乘以及其逆元,然后直接使用 \(\tbinom{n}{m}=\tfrac{n!}{m!(n-m)!}\) 即可。
const int N = 20000010;
const int MOD = 19260817;
const int INF = 0x3fffffff;
ll t,k,n,m,tms[N],inv[N],tmsinv[N],ans,coe[2][3];
ll p[3] = {1,19260817,370979071507489};
inline void pre_c(){
tms[0] = 1,tmsinv[0] = 1,inv[1] = tmsinv[1] = 1;
for (int i = 1;i < N;i ++)
tms[i] = (tms[i - 1] * i) % MOD;
for(int i = 2;i <= MOD;i ++){
inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
tmsinv[i] = tmsinv[i - 1] * inv[i] % MOD;
}
}
inline ll C(ll i,ll j){
if (i < j) return 0;
if (j == 0) return 1;
return ((tms[i] * tmsinv[j]) % MOD) * tmsinv[i - j] % MOD;
}
inline void get_coe(ll x,int rel){
int last = 2;
while (x > 0){
while (x < p[last])
last --;
coe[rel][last] = x / p[last];
x -= coe[rel][last] * p[last];
}
}
int main(){
pre_c();
scanf("%lld",&t);
while (t --){
scanf("%lld%lld",&n,&m);
ans = 1;
mset(coe,0);
get_coe(m + n + 2,0);
get_coe(m + 1,1);
for (int i = 0;i <= 2;i ++)
ans = (ans * C(coe[0][i],coe[1][i])) % MOD;
printf("%lld\n",((ans - 2) % MOD + MOD) % MOD);
}
return 0;
}
#3.0 补充证明
#3.1 朱世杰恒等式
考虑 \((x+1)^{n+m+1}\) 中项 \(x^{m+1}\) 的系数.
柿子左边的项 \(x^{m+1}\) 的系数显然是 \(\tbinom{n+m+1}{m+1}\),再来看右边。
我们考虑对第一个 \(x\) 选取的位置进行分类,第一个 \(x\) 最小在第一个柿子里取,最大在第 \(n+1\) 个柿子里取,剩下 \(m\) 个 \(x\) 在其后面的几项中取,故其系数应为:
那么,等式两边 \(x^{m+1}\)的系数应当是相等的,所以有
#3.2 Lucas 定理
易知,当 \(1\leq j \leq p^i-1,i\geq1\) 时,\(\tbinom{p^i}{j}\) 是 \(p\) 的倍数,故
所以有
上式左端 \(x^m\) 的系数为 \(\tbinom{n}{m}\),考虑到 \(m\) 表示成 \(p\) 进制的唯一性,\(m=b_0+b_1p^1+\cdots+b_kp^k\),故上式右端 \(x^m\) 的系数应为
故有
在考后讲解时遇到了这样的问题:
\(b_i\) 有可能大于 \(a_i\),那么此时式 \((2)\) 仍成立吗?
仍成立。
证明:
根据组合数的定义,我们知道,当 \(a_i<b_i\) 时,\(\tbinom{a_i}{b_i}=0\),所以式 \((2)\) 右端的柿子等于零;再来看左边的柿子:
所以得
不难发现,在式 \((3)\) 右边出现项 \(x^{b_ip^i}\) 是有可能的,我们将 \([1+x^{p^i}+\sum\limits_{j_i=1}^{p^i-1}\dbinom{p^i}{j_i}x^{j_i}]^{a_i}\) 展开,得到:
显然有多种选取方法可以得到 \(x^{b_ip^i}\),比如在第一个柿子中选 \(x^{p^i}\),在第二个式子中选 \(x^{b_i}\),其他的柿子都选 \(1\),最终的结果即为 \(x^{b_ip^i}\)。但注意到,无论怎样选取,必然有至少一个因子是从 \(\sum_{j_i=1}^{p^i-1}\tbinom{p^i}{j_i}x^{j_i}\) 中选取得到的,所以项 \(x^{b_ip^i}\) 前的系数至少有一个为 \(\tbinom{p^i}{j_i},1\leq j_i\leq p^i-1\),而我们知道 \(p|\tbinom{p^i}{j_i},1\leq j_i\leq p^i-1\),故上式在模 \(p\) 意义下时,设 \(x^{b_ip^i}\) 的系数为 \(\alpha\),必然有
所以 \(x^{b_ip^i}\) 的系数在模 \(p\) 意义下为 \(0.\)
那么,在由相乘得到 \(x^m=x^{b_0+b_1p^1+\cdots+b_kp^k}\) 时,自然项 \(x^m\) 的系数也为 \(0\),所以,式 \((3)\) 左边项 \(x^m\) 的系数在模 \(p\) 意义下为 \(0\),而单独分析式 \((3)\) 左端柿子中 \(x^m\) 的系数应为 \(\tbinom{n}{m}\),所以可知
即得式 \((2)\) 在 \(\exists a_i<b_i\) 时仍成立。