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] 连通图计数
错误思路
然而并没有思路:(