4月模考
死亡回放 模考时间线
- 1:30 比赛开始,读
T1 宇宙爆炸
的题 - 1:50 自己手模了几组样例,得出了一个错误结论,打出了第一版错误代码,然后上交( Wrong Answer 20 \color{red}\text{Wrong\ Answer\ 20} Wrong Answer 20)
- 中间拿了
T2 逃狱风云
的部分分( Wrong Answer 10 \color{red}\text{Wrong\ Answer\ 10} Wrong Answer 10,最终分数),之后在想T3 奇怪物语
,然后推出需要用到二进制,但是想不出来,交了一个暴搜(?( Time Limit Exceeded 20 \color{blue}\text{Time\ Limit\ Exceeded\ 20} Time Limit Exceeded 20) - 3:00 返回去检查
T1 宇宙爆炸
正确性,发现果然有问题!迅速修改了一下,把栈改成队列,再交了一发( Wrong Answer 60 \color{red}\text{Wrong\ Answer\ 60} Wrong Answer 60) - 然后发现还是有问题!于是又改了改交了一发( Accepted 100 \color{green}\text{Accepted\ 100} Accepted 100)
- 可是我不自信啊!就把部分分改成了暴力,然后暴力挂了!( Wrong Answer 65 \color{red}\text{Wrong\ Answer\ 65} Wrong Answer 65,最终分数)
- 后面的时间要么在发呆,要么在狂想
T3 奇怪物语
,中间好不容易意识到写个递推拿的分更多,但是 5 × 1 0 7 5\times 10^7 5×107 的空间复杂度我开成了long long
( Time Limit Exceeded 0 \color{blue}\text{Time\ Limit\ Exceeded\ 0} Time Limit Exceeded 0,最终分数) T4 东部世界
就没有去推了qwq
死因分析 错误点分析
- 应该多去拿每个题的部分分的,简单的式子都去推一下!
- 暴力也得去验证是否正确!
- 应该要意识到空间复杂度的影响!
- 题目中给的提示得最大化使用!
死亡证明 成绩
遗书 各题错误思路 & 正解
T1 宇宙爆炸
给定一个由等量的 0 0 0 和 1 1 1 组成的环 s s s,各有 n n n 个。对于每一个 1 1 1,如果其逆时针方向上第一个元素为 0 0 0,则它们会被一起删除,环会重新接上,这个过程一直重复直到不存在可以删除的元素。现在要求出一些位置,使得在这些位置插入一个 1 1 1 后,这个 1 1 1 不会被删除。
对于所有数据,保证: 1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1≤n≤106, s i ∈ { 0 , 1 } s_i\in\{0,1\} si∈{0,1}。
错误思路
就不该加暴力!也不知道暴力哪错了……
正解
场上想的:遍历两遍环,开一个队列存当前这个环(不同起点),在队列里执行湮灭操作;在第二遍遍历的时候,如果发现将 x x x 加入队列后发生湮灭,且湮灭完了队列为空,则说明 x x x 后面一个元素是安全的。这很好想:如果某一时刻还剩下的元素没有被湮灭完,那么此时加入肯定会代替后面的某个元素进行湮灭操作。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
char s[maxn << 2]; int n;
int q[maxn << 3],ans[maxn << 1],tot; int l,r;
int main() {freopen("bigbang.in","r",stdin);freopen("bigbang.out","w",stdout);scanf("%d%s",&n,s + 1);n <<= 1; for (int i = 1;i <= n;i ++)s[i + n] = s[i];l = 1,r = 0;for (int i = 1;i <= (n << 1);i ++) {while (l <= r && q[l] <= i - n) l ++;if (l > r) { q[++ r] = i; continue; }if (s[q[r]] == '1' && s[i] == '0') {r --;if (l > r && i > n) ans[++ tot] = i - n;} else q[++ r] = i;}sort(ans + 1,ans + tot + 1);int x = unique(ans + 1,ans + tot + 1) - ans - 1;for (int i = 1;i <= x;i ++) printf("%d%c",ans[i]," \n"[i == x]);return 0;
}
T2 逃狱风云
各有 n n n 个犯人、盒子和纸条,编号均为 1 → n 1\to n 1→n。每张纸条随机放入不同的每个盒子里。每个犯人都需要按照一定策略在 n n n 个盒子中挑选 k k k 个打开,如果没有看见自己编号的纸条则所有人都将被处决。否则全员释放。求在最优策略下全员释放的概率。犯人与犯人之间无影响。
提示:最优策略为: i i i 号犯人先打开 i i i 号盒子,然后根据看到的纸条编号打开对应编号的盒子,直到 k k k 次用完或看到自己的纸条。
错误思路
压根不觉得这个提示是最优策略,以为只是针对于该样例而言可能的一个策略。然后就没有了,拿了 10 10 10 pts部分分。
正解
按照提示给定的策略,按照盒子与纸条的关系建一张图 G G G,如果 i i i 号盒子中放着 j j j 号纸条,则 i i i 像 j j j 连一条有向边。 G G G 中一共 n n n 个点、 n n n 条边,且每个点各有一条入边、出边。 G G G 一定由若干个互不相交的环组成。如果所有环的大小均不大于 k k k,那么所有犯人都能通过 k k k 步走到自己编号的点上。则题目转化为求 n ! n! n! 种图中所有环的大小都不大于 k k k 的概率。
令 f i f_i fi 表示 i i i 个点的图中,所有环大小均不大于 k k k 的张数, g i g_i gi 表示概率。显然 g i = f i i ! g_i=\cfrac{f_i}{i!} gi=i!fi,且对于 i ≤ k i\le k i≤k, f i = i ! , g i = 1 f_i=i!,g_i=1 fi=i!,gi=1。转移 f i f_i fi 时枚举包含第 i i i 个点的环的大小 j j j( j ≤ k j\le k j≤k),则这个环不包含 j j j 会有 A i − 1 j − 1 A^{j-1}_{i-1} Ai−1j−1 种形态,剩下 i − j i-j i−j 个点合法的方案数为 f i − j f_{i-j} fi−j,故转移方程为:
f i = ∑ j = 1 k A i − 1 j − 1 × f i − j = ∑ j = 1 k ( i − 1 ) ! ( i − j ) ! f i − j f_i=\sum^k_{j=1}A^{j-1}_{i-1}\times f_{i-j}=\sum^k_{j=1}\cfrac{(i-1)!}{(i-j)!}f_{i-j} fi=j=1∑kAi−1j−1×fi−j=j=1∑k(i−j)!(i−1)!fi−j
此时把 f i f_i fi 推广到 g i g_i gi,可以得到最终的递推式:(把 ( i − 1 ) ! (i-1)! (i−1)! 提到求和之前)
g i = 1 i ! f i = 1 i ! ∑ j = 1 k ( i − 1 ) ! ( i − j ) ! f i − j = ( i − 1 ) ! i ! ∑ j = 1 k f i − j ( i − j ) ! = 1 i ∑ j = 1 k g i − j \begin{align}g_i&=\cfrac{1}{i!}f_i\\&=\cfrac{1}{i!}\sum^k_{j=1}\cfrac{(i-1)!}{(i-j)!}f_{i-j}\\&=\cfrac{(i-1)!}{i!}\sum^k_{j=1}\cfrac{f_{i-j}}{(i-j)!}\\&=\cfrac{1}{i}\sum^k_{j=1}g_{i-j}\end{align} gi=i!1fi=i!1j=1∑k(i−j)!(i−1)!fi−j=i!(i−1)!j=1∑k(i−j)!fi−j=i1j=1∑kgi−j
后面的求和部分可以在计算 g g g 的时候前缀和,预处理出 1 i \cfrac{1}{i} i1 的逆元,复杂度 O ( n ) O(n) O(n)。预处理逆元的方法:也是递推,初始 i n v 1 = 1 inv_1=1 inv1=1,递推式为: i n v i = ( ( P − ⌊ P i ⌋ ) × i n v P m o d i ) m o d P inv_i=((P-\lfloor\cfrac{P}{i}\rfloor)\times inv_{P\bmod i})\bmod P invi=((P−⌊iP⌋)×invPmodi)modP。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e6 + 5;
int n,k;
const int P = 998244353;
ll sum[maxn],g[maxn],inv[maxn];
int main() {freopen("prison.in","r",stdin);freopen("prison.out","w",stdout);scanf("%d%d",&n,&k);inv[1] = g[1] = sum[1] = 1;for (int i = 2;i <= k;i ++)inv[i] = ((P - P / i) * inv[P % i]) % P,g[i] = 1, sum[i] = (sum[i - 1] + 1) % P;for (int i = k + 1;i <= n;i ++)inv[i] = ((P - P / i) * inv[P % i]) % P,g[i] = inv[i] * ((sum[i - 1] - sum[i - k - 1]) % P + P) % P,sum[i] = (sum[i - 1] + g[i]) % P;printf("%lld",g[n]);return 0;
}
T3 奇怪物语
有 Q Q Q 次询问,每次询问给定一个数 N N N,求有多少种方法,使得从 1 1 1 开始只通过 + 1 / × 2 / × 2 + 1 +1/\times 2/\times2+1 +1/×2/×2+1 三种操作若干次后能得到 N N N。 Q ≤ 200 Q\le 200 Q≤200, N ≤ 1 0 18 N\le 10^{18} N≤1018。
错误思路
前 50 50 50 pts 用的递推,后面用暴搜 + 记忆化,但是没有去算 5 × 1 0 7 5\times 10^7 5×107 还开 long long
的空间复杂度,最终导致又 MLE 又 TLE……😦
正解
对于转移方程 f ( i ) = f ( ⌊ i 2 ⌋ ) + f ( i − 1 ) f(i)=f(\lfloor\cfrac{i}{2}\rfloor)+f(i-1) f(i)=f(⌊2i⌋)+f(i−1),考虑使用矩阵优化(可以看看这篇博客),宗旨即为:构造一个答案矩阵和转移矩阵,通过转移矩阵的矩阵快速幂与答案矩阵相乘加速转移。对于两个矩阵:
$$
A_{n\times m}=
\begin{bmatrix}
a_{1,1}& a_{1,2}& \cdots & a_{1,m} \
a_{2,1}& a_{2,2}& \cdots & a_{2,m} \
\vdots & \vdots & \ddots & \vdots \
a_{n,1}& a_{n,2}& \cdots & a_{n,m}
\end{bmatrix}\ \ ,\ \
B_{m\times p}=
\begin{bmatrix}
b_{1,1}& b_{1,2}& \cdots & b_{1,p} \
b_{2,1}& b_{2,2}& \cdots & b_{2,p} \
\vdots & \vdots & \ddots & \vdots \
b_{m,1}& b_{m,2}& \cdots & b_{m,p}
\end{bmatrix}
$$
对他们进行矩阵乘法运算得到 C n × p C_{n\times p} Cn×p(可见,矩阵乘法得到的答案矩阵长宽与 A , B A,B A,B 的顺序有关,故矩阵乘法不具有交换律,但是有结合律,所以能用快速幂加速): C ( i , j ) = ∑ k = 1 m A ( i , k ) × B ( k , j ) C(i,j)=\sum^m_{k=1}A(i,k)\times B(k,j) C(i,j)=∑k=1mA(i,k)×B(k,j)。
以一个经典的题目为例:
求斐波那契数列的第 n n n 项 F ( n ) F(n) F(n)。 n ≤ 1 0 18 n\le 10^{18} n≤1018。
对于此题,我们维护的答案矩阵即为:
A n s ( n ) = [ F ( n − 1 ) F ( n − 2 ) ] Ans(n)=\begin{bmatrix}F(n-1)&F(n-2)\end{bmatrix} Ans(n)=[F(n−1)F(n−2)]
当然也可以竖过来写,效果几乎相同,转移矩阵即为:
M = [ 1 1 1 0 ] M=\begin{bmatrix}1&1\\1&0\end{bmatrix} M=[1110]
F ( 1 ) = F ( 2 ) = 1 F(1)=F(2)=1 F(1)=F(2)=1,所以 A n s ( 3 ) = [ 1 1 ] Ans(3)=\begin{bmatrix}1&1\end{bmatrix} Ans(3)=[11],要求的 F ( n ) F(n) F(n) 即为 A n s ( 3 ) × M n − 2 Ans(3)\times M^{n-2} Ans(3)×Mn−2 得到的矩阵的第一行第一列元素。可以把乘法过程写出来看看原理。而就像任何数乘上 1 1 1 都不变一样,矩阵中的 1 1 1 即为朝向右下角的对角线上全为 1 1 1、其余为 0 0 0 的正方形矩阵。
回到本题。因为
f ( i + 1 ) = f ( i ) + f ( ⌊ i + 1 2 ⌋ = f ( i ) + f ( ⌈ i 2 ⌉ ) ) f(i+1)=f(i)+f(\lfloor\cfrac{i+1}{2}\rfloor=f(i)+f(\lceil\cfrac{i}{2}\rceil)) f(i+1)=f(i)+f(⌊2i+1⌋=f(i)+f(⌈2i⌉))
考虑维护的答案矩阵为
F ( i ) = [ f ( i ) f ( ⌈ i 2 ⌉ ) f ( ⌈ i 4 ⌉ ) ⋮ f ( 1 ) ] F(i)=\begin{bmatrix}f(i) \\f(\lceil\cfrac{i}{2}\rceil) \\f(\lceil\cfrac{i}{4}\rceil) \\\vdots \\f(1) \end{bmatrix} F(i)= f(i)f(⌈2i⌉)f(⌈4i⌉)⋮f(1)
转移时,对于所有非负整数 k k k,满足 ⌈ i + 1 2 k ⌉ = ⌈ i 2 k ⌉ \lceil\cfrac{i+1}{2^k}\rceil=\lceil\cfrac{i}{2^k}\rceil ⌈2ki+1⌉=⌈2ki⌉ 或 ⌈ i + 1 2 k ⌉ = ⌈ i 2 k ⌉ + 1 \lceil\cfrac{i+1}{2^k}\rceil=\lceil\cfrac{i}{2^k}\rceil+1 ⌈2ki+1⌉=⌈2ki⌉+1。对于前者,转移方程为 f ( ⌈ i + 1 2 k ⌉ ) = f ( ⌈ i 2 k ⌉ ) f(\lceil\cfrac{i+1}{2^k}\rceil)=f(\lceil\cfrac{i}{2^k}\rceil) f(⌈2ki+1⌉)=f(⌈2ki⌉);对于后者,转移方程为 f ( ⌈ i + 1 2 k ⌉ ) = f ( ⌈ i 2 k ⌉ + 1 ) = f ( ⌈ i 2 k ⌉ ) + f ( ⌈ i 2 k + 1 ⌉ ) f(\lceil\cfrac{i+1}{2^k}\rceil)=f(\lceil\cfrac{i}{2^k}\rceil+1)=f(\lceil\cfrac{i}{2^k}\rceil)+f(\lceil\cfrac{i}{2^{k+1}}\rceil) f(⌈2ki+1⌉)=f(⌈2ki⌉+1)=f(⌈2ki⌉)+f(⌈2k+1i⌉)。如果某个 k 0 k_0 k0 满足第一种情况,那么对于所有的 k ≥ k 0 k\ge k_0 k≥k0 都会满足第一种情况。所以只需考虑最小的 k 0 k_0 k0 即可,两种情况被 k 0 k_0 k0 所分割。而后一种情况成立,当且仅当 2 k 2^k 2k 能整除 i i i,等价于 i i i 的二进制下末尾的 0 0 0 的个数大于等于 k k k。于是 k 0 k_0 k0 只和 lowbit ( i ) \operatorname{lowbit}(i) lowbit(i) 有关。
提一嘴:对一个竖着的只有一列的矩阵 M M M 去乘转移矩阵时,欲使转移过程中 M i + M i + 1 M_i+M_{i+1} Mi+Mi+1 即相邻两项求和,只需在转移矩阵中对应位置 ( i , i + 1 ) (i,i+1) (i,i+1) 处把 0 0 0 改成 1 1 1 即可。我们设从 F ( i ) F(i) F(i) 转移到 F ( i + 1 ) F(i+1) F(i+1) 的转移矩阵为 M ( i ) M(i) M(i),使得 F ( i + 1 ) = F ( i ) × M ( i ) F(i+1)=F(i)\times M(i) F(i+1)=F(i)×M(i),显然 M ( i ) M(i) M(i) 长宽均为 ⌈ log i ⌉ \lceil\log i\rceil ⌈logi⌉。同理 M ( i ) M(i) M(i) 中添 1 1 1 的位置也只和 lowbit ( i ) \operatorname{lowbit}(i) lowbit(i) 有关。所以最终的 M M M 画出来一定长成:对角线上前一段会有 2 2 2 个 1 1 1,后一段会有 1 1 1 个 1 1 1,对应之前的两种情况。所求的 F ( n ) F(n) F(n) 即为
F ( n ) = ( ∏ i = n − 1 1 M ( i ) ) × F ( 1 ) F(n)=\Big(\prod_{i=n-1}^1M(i)\Big)\times F(1) F(n)=(i=n−1∏1M(i))×F(1)
利用树状数组的思想,找到一个 m m m 使得 2 m 2^m 2m 是小于等于 n − 1 n-1 n−1 的最大 2 2 2 的整数次幂,那么
( ∏ i = n − 1 1 M ( i ) ) × F ( 1 ) = ( ∏ i = n − 1 2 m M ( i ) ) ( ( ∏ i = 2 m − 1 1 M ( i ) ) × F ( 1 ) ) = ( ∏ i = n − 2 m − 1 1 M ( i ) ) ( ( ∏ i = 2 m − 1 1 M ( i ) ) × F ( 1 ) ) \begin{align} \Big(\prod_{i=n-1}^1M(i)\Big)\times F(1)&=\Big(\prod^{2^m}_{i=n-1}M(i)\Big)\Big(\big(\prod^1_{i=2^m-1}M(i)\big)\times F(1)\Big)\\&=\Big(\prod^1_{i=n-2^m-1}M(i)\Big)\Big(\big(\prod^1_{i=2^m-1}M(i)\big)\times F(1)\Big) \end{align} (i=n−1∏1M(i))×F(1)=(i=n−1∏2mM(i))((i=2m−1∏1M(i))×F(1))=(i=n−2m−1∏1M(i))((i=2m−1∏1M(i))×F(1))
这里将大于 2 m 2^m 2m 的下标都减去 2 m 2^m 2m,前后不改变 lowbit \operatorname{lowbit} lowbit 的值,则转移矩阵也相同。最后,我们考虑预处理出每一个 M ( 2 m ) × M ( 2 m − 1 ) × ⋯ × M ( 1 ) M(2^m)\times M(2^m-1)\times\dots\times M(1) M(2m)×M(2m−1)×⋯×M(1) 的值,我们设其为 P ( 2 m ) P(2^m) P(2m)。先预处理出 M ( 2 m ) M(2^m) M(2m) 的值,则
P ( 2 m ) = ( M ( 2 m − 1 − 1 ) × ⋯ × M ( 1 ) ) × M ( 2 m − 1 ) × ( M ( 2 m − 1 − 1 ) × ⋯ × M ( 1 ) ) = P ( 2 m − 1 ) × M ( 2 m − 1 ) × P ( 2 m − 1 ) \begin{align}P(2^m)&=(M(2^{m-1}-1)\times \dots \times M(1))\times M(2^{m-1})\times (M(2^{m-1}-1)\times \dots \times M(1))\\&=P(2^{m-1})\times M(2^{m-1})\times P(2^{m-1})\end{align} P(2m)=(M(2m−1−1)×⋯×M(1))×M(2m−1)×(M(2m−1−1)×⋯×M(1))=P(2m−1)×M(2m−1)×P(2m−1)
对于计算最终的 f ( n ) f(n) f(n),即 F ( n ) F(n) F(n) 的第一行,对于 n n n 二进制上第 i i i 位为一时, F ( n ) ← M ( 2 i ) × P ( 2 i ) × F ( n ) F(n)\gets M(2^i)\times P(2^i)\times F(n) F(n)←M(2i)×P(2i)×F(n)。这个过程代替了快速幂。计算时从右往左计算(矩阵乘法的结合律),可以降低每一轮乘法的时间复杂度。
预处理 M , P M,P M,P 花去 O ( log 4 n ) O(\log^4n) O(log4n),回答每一轮花去 O ( log 3 n ) O(\log^3n) O(log3n),总时间复杂度 O ( log 4 n + q log 3 n ) O(\log^4n+q\log^3n) O(log4n+qlog3n)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll MOD = 998244353;
const ll maxn = 1e18;
const int maxm = 60;
struct Mat { // 矩阵板子ll a[maxm + 5][maxm + 5];int n,m;Mat(int x = maxm,int y = maxm) {n = x, m = y;for (int i = 1;i <= n;i ++)for (int j = 1;j <= m;j ++)a[i][j] = 0;}void draw(int k0) {for (int i = 1;i < k0;i ++) // 相邻的求和a[i][i] = a[i][i + 1] = 1;for (int i = k0;i <= m;i ++) // 其余直接等号等过去a[i][i] = 1;}Mat operator* (const Mat &X) const {Mat res = Mat(n, X.m);for (int i = 1;i <= res.n;i ++)for (int j = 1;j <= res.m;j ++)for (int k = 1;k <= m;k ++)(res.a[i][j] += a[i][k] * X.a[k][j] % MOD) %= MOD;return res;}
} M[maxm + 5], P[maxm + 5], F;
int Q; ll n;
void init() {F = Mat(maxm,1);for (int i = 1;i <= F.n;i ++)F.a[i][1] = 1;
}
int main() {freopen("stranger.in","r",stdin);freopen("stranger.out","w",stdout);for (int i = 1;i <= maxm;i ++) // 转移矩阵M[i].draw(i + 1);for (int i = 1;i <= P[1].n;i ++) // 开始预处理累乘P[1].a[i][i] = 1ll;for (int i = 2;i <= maxm;i ++)P[i] = P[i - 1] * M[i - 1] * P[i - 1];scanf("%d",&Q);while (Q --) {scanf("%lld",&n);init();for (int i = maxm;i > 0;i --) { // 快速幂的变相写法if (((n - 1) >> (i - 1)) & 1)F = M[i] * (P[i] * F);}printf("%lld\n",F.a[1][1] % MOD);}return 0;
}
T4 东部世界
洛谷原题链接:P9535 [YsOI2023] 连通图计数
错误思路
然而并没有思路:(
正解
洛谷题解