一些题
有些内容因为机房电脑死机而丢失,这里标记为 TODO
根式指数和
Statement
求
(若 nmod2=1,答案为 0;否则上式中的 n 为实际输入的 n/2)
给出了 n(≤109),m(≤7),ai
Solution
DP:
f(i,S) 表示 n=i,akii 的 ki 的奇偶性:若 i∈S,ki 为奇数,否则 ki 为偶数;的答案
Ans=f(n,∅)
考虑 √ai 是个实数,不适合放到矩阵乘法中,优化一下式子:
感性理解,非常的对
于是矩阵乘法优化 DP,做完了!!!!!!!
宝石路线
Solution
设 f(t,u,S) 表示 1→u 经过了 t 条边,收集到的宝石集合为 S 的方案数
设一条边 (u,v,T) 表示 u→v 的有向边,上面的宝石集合为 T
f(0,1,∅)=1
Ans=∑uf(T,u,{1,2,3,4})
考虑优化,一个 (n×24)×1 的矩阵 A(u,S) 表示 f(t,u,S)
A(1,∅)=1
(n×24)×(n×24) 的转移矩阵 B(v,P),(u,S)=[∃(v,u,T)∈E∧P∪T=S]
Res←BTA
Ans=∑iResi,1
时间 (24n)3logT
Solution
列出限制条件:Ans=|有红∩有黄∩有蓝∩有绿|
这有点难求,直接转化:Ans=|U|−|无红∪无黄∪无蓝∪无绿|
考虑容斥,系数为 (−1)限制条数
问题变成形如:限定这个图中只能走某些“无红且无黄”的边,走到某个点的方案数
设可走的边集为 E′
设 f(t,u) 表示 1→u 走 t 步的方案数
f(0,1)=1
考虑矩乘优化:
A 为 n×1 的矩阵,初始 A1,1=1
B 为 E′ 邻接矩阵的转置
Res=BTA(这个 T 是指 B 的 T 次方)
做完了,因为容斥要做 24 次这个问题,复杂度 O(24n3logT)
指数乘多项式形式2
Statement
n≤109,∑pi=1qi≤100.
Solution
设 L=pmaxi=1qi
考虑 n→n+1,我们的目标是维护每个 bninj,考虑
所以得出答案:
时间:O(n3logT)
路线方案数
Solution
设 f(t,u) 为 1 走 t 距离到 u 的方案数,f(0,1)=1
设 m=maxw≤10
考虑优化,先设一个 nm×1 的矩阵 A,A(t,u),1 表示 f(t,u)
再设一个 nm×nm 的转移矩阵 B,对于前 m−1 个 t,B(t,u),(t+1,u)=1,否则 B(t,u),(s,v)=[∃(v,u,w)∈E,s=t−w]
A 初始时 A(0,1),1=1,0 为 A 中最大的 t
令 R=BTA(这里 BT 是指 B 的 T 次方)
R(T,u),1 即为每个 u 的答案
时间:O((nm)3logT),nm≤100
生日悖论
Solution
考虑
解得
前缀染黑期望步数
Solution
设 P(i) 表示染了 m 次后,恰好前 i 个格子被染黑的概率
于是
找回帽子问题
Solution
设 D(n) 为 n 错排数
D(n) 有很多求法,举个例子,用容斥算:
用递推算:
飞行棋问题
Solution
定义距离终点 [0..d] 的位置区间为安全区。
这个游戏有两个阶段:
- 距离终点距离 >d,这时他会一直朝终点走
- 距离终点距离 ≤d,这时他会一直在安全区内游走
考虑第一个阶段:
设 f(i,j) 为从距终点 i+d 的地方开始,花 j 步进入安全区的概率。
考虑第二个阶段:
设 q 为两人都在安全区内,先手获胜的概率
考虑结合两个阶段,求出答案:
设 P(a,b) 为先手用 a 步进入安全区,后手用 b 步进入安全区,先手获胜的概率
那么
考虑优化空间:滚动 f 的一维即可。
一无所有
Solution
先分别去除 A,B 中被严格偏序的无用点,然后分别按 x 排序
证明贡献满足四边形不等式:如下图
然后根据决策单调性,即可分治解决。
投币取石子问题
Solution
设 f(i,0/1) 为还有 i 颗石子,先手为 A/B 时 A 的获胜概率
则 f(0,0)=0,f(0,1)=1
若 f(i−1,0)>f(i−1,1):
若 f(i−1,0)≤f(i−1,1),同理可得:
答案:f(n,0)
无向图碰面期望步数
Solution
设 f(u,v) 为 A 在点 u,B 在点 v 的期望碰面次数,其中 u,v 连通
设 S(u) 为 u 以及与 u 相邻的所有点组成的集合,p(u,v) 为 S(v) 中距 u 最近的点中编号最小的一个.
f(i,i)=0
不能 O(n3) 直接消元,考虑如何没有后效性地转移.
注意到每过一个单位时间,A,B 的距离一定会减少,于是按 u,v 的距离从小到大转移即可
收集邮票
Statement
n 个数,每次等概率随机选一个数,设收集到所有数的时间为 t,求 E(t(t+1)2)
Solution
设 si 表示目前已经有 i 种邮票,花多少步到达 n
设 f(i)=E(si)
设 g(i)=E(s2i)
f(n)=g(n)=0
更简单的方法:(sto sto sto sto sto 国队 orz orz orz orz orz)
设 f(i) 为 i→i+1 期望花费多少
为了转移,还需要设 g(i) 为 0→i 期望多少步、h(i) 为 i→i+1 期望多少步
之类的式子,我也记不清楚了(应该是这个)
然后考虑 g(i) 里面每个 ni 的贡献次数,即可得到最简式子。
弱题
Statement
M 个数,值域 [1..N],操作 K 次,每次选一个数 x 然后 x←xmodN+1,问最后 1∼N 每个数的期望出现次数
N≤103,M≤109,K≤231−1
Solution
朴素 DP:设 f(i,k) 为第 k 轮答案
这里 i−1 值域在 [1..N] 内
然后循环矩阵优化,O(n2logK)
所以为什么是卡特兰数?
Statement
LIS≤2 的排列计数,n≤2⋅106.
Solution
根据 Dilworth 定理,条件等价于可以划分为最多两个下降子序列。
先考虑对于一个排列,如何计算他能被最少划分为多少个下降子序列:
设 di 为第 i 个下降子序列的末尾值,初始为 inf,考虑贪心:
考虑加入一个数 x,取出所有末尾值中第一个 ≥x 的,然后修改为 x.
原问题相当于只有 d1,d2 两个下降子序列,d1<d2
根据上述贪心,考虑 DP 算出原问题的答案:
设 f(i,j) 为已选前 i 个数,有 j 个小于 d1 的未选数的方案数,有 i+j≤n
有两种转移:
- 下一个数接在 d2 后面,该数一定为未选数的最大值
- 下一个数接在 d1 后面,该数 <d1
边界:f(0,n)=1
答案:f(n,0)
注意到 f(i) 为 f(i−1) 的前缀和,故容易转化为 g(i,j)=g(i−1,j)+g(i,j−1)
注意到其几何意义,就是从 (0,n) 开始,每次往下或者往右移一步,不能越过 y=n−x,到达 (n,0) 的方案数
所以他是卡特兰数。
翻硬币
Solution
手动消元!
f(i) 当前有 i 个正面朝上的硬币,到 n 个正面朝上的期望次数。
f(k)=0
然后手动消元。
咋校园呢:
此时将 f(n−1) 代入 f(n−2) 的式子再化简,就变成了只与 f(n−3) 有关的一个式子
将 f(n−2) 类似地代入。。。。
f(0) 没有 f(−1),也就是得到 f(0) 是某个数
然后再将 f(0) 反代入 f(1),f(2),…
得出所有 f。
具体过程:
设 f(i)=Aif(i−1)+Bi
An−1=n−1n,Bn−1=1
An−i=n−in−i⋅An−i+1
Bn−i=i⋅Bn−i+1+nn−i⋅An−i+1
f(0)=B0
然后反推回去
好麻烦呀!有没有更简单的方法呢?
一种更简单的方法:(sto sto sto sto 蝈対 orz orz orz orz)
设 f(i) 为从 i→i+1 的期望步数
解得
做后缀和然后输出即可。
骰子棋2
Solution
国队:
方程直接列。
消元直接消。
设 f(i) 为 i 到 n 的期望步数。
f(n)=0
猴子大战
Solution
首先朴素 DP:
设 P(S) 为集合 S 的胜率,令 T=U−S
下面是一个本质相同的式子,没啥区别
考虑这题咋做。
结论:P(S∪T)=P(S)+P(T)
考虑 S,T,R 三人玩游戏,根据常识有 P(S)+P(T)+P(R)=1
考虑 S∪T,R 两人玩游戏,根据常识有 P(S∪T)+P(R)=1
所以 P(S)+P(T)=P(S∪T)
于是只考虑所有单个牌的集合的胜率即可!
设 f(i) 为牌 i 的胜率,照着最上面的式子有
还有 ∑f(i)=1
高斯消元即可。
卡牌游戏
Solution
设 f(i,j) 为剩下 i 个人时,从庄家开始第 j 个人的存活概率
f(1,1)=1
集邮问题2
Solution
设 f(S) 为当前有 S 集合的卡,到集齐所有卡还需要期望多少步。
化简得
设全集为 U,f(U)=0
答案为 f(∅)
奖牌魔法
Solution
设 f(a,b,c) 为当前有 a 块铜牌 b 块银牌 c 块金牌,达到目标状态的期望耗费魔力数
f(0,0,n)=0
因为 a+b+c=n,有效的状态数是 O(n2) 的,所以直接高斯消元即可
我的电影
Statement
长度为 n 的序列,值域 1∼m
定义一段区间的权值为,其所有仅出现过一次的数 x 的 wx 之和
求最大权值
Solution
简单题。
扫一遍
考虑加入一个数 x,设其上一次出现位置为 p,其上上次出现位置为 q
那么 [q+1..p] 区间减 wx,[p+1..i] 区间加 wx,每次查区间 max
做完了。
这个 trick 是“区间本质不同子串数”的严格子集。
奇怪的植物
Solution
简单题。
楼房重建
把 push_up 改成 O(log) 的就行了。
顺便学一下兔队线段树:
兔队线段树
一种维护前缀 max 序列的方法。
本题是要求动态维护严格前缀 max 序列的长度。
记 si 为 i 的斜率
一个节点 [l,r] 维护:
- 本区间 si 的 max
- 仅考虑这个区间时,前缀 max 序列长度。也就是不考虑 [1,l−1] 区间对本区间的影响。
第二个信息 push_up 时直接线段树二分,记 calc(u,x) 为 u 子树内,只考虑 >x 的数时的答案,有
def calc(u, x): if u is a leaf node: return [u.mx > x] if u.ls.mx > x: return calc(u.ls, x) + (u.ans - u.ls.ans) else: return 0 + calc(u.rs, x)
这题就做完了。
注意到上述 calc
利用了 ans
的可减性(u.ans - u.ls.ans
),那么若 ans
没有可减性咋做捏?
仔细思考,把 ans
的定义改成“考虑该区间的影响后,右子树的答案”即可!
对于叶子,其 ans
视作未定义。
然后修改 calc
函数,只需要把 u.ans - u.ls.ans
改为 u.ans
即可。
最小差值序列
Slope Trick。当时学了一通。
TODO
凑整数
同余最短路 / 模意义下的完全背包的转圈做法 板子题。
单格染黑期望步数
Solution
设 f(i) 为从 i 个黑色格子到 i+1 个黑色格子的染色次数期望
答案:∑n−1i=0f(i)
n元一次不定方程
Statement
求
的正整数解数量 mod(109+7)
n≤15,ai≤15,m≤109
Solution
首先 m←m−n∑i=1ai,问题变成非负整数解
设 L=lcmni=1ai,gi=Lai
那么研究一组解 (x1,x2,…,xn):
设 xi=kigi+bi,其中 0≤bi<gi
设 K=∑ni=1ki
其中 0≤bi<gi
可以发现它们的和由两部分组成:KL+n∑i=1aibi
所以可以分别对两部分计算方案数,然后组合起来。
Part I
设 K 的一种分解方案 K=∑ni=1ci,ci≥0
那么这就唯一对应一组解:
问题变成对 K 的分解方案进行计数。
这是容易的:(K+n−1n−1)
Part II
首先 L≤360360<4⋅105
由于 bi<gi,故 n∑i=1aibi<nL<6⋅106
所以直接做背包求方案数,计算量 <1⋅108,非常充裕!
Part III
考虑枚举 Part II 中的背包,每枚举到 i≡m(modL) 就计算方案数。
这里我们会计算至多 nLL=n 次。
剩下唯一的问题在于求组合数。
发现每次直接 O(n) 做就行了!
复杂度:O(n2L)
Code
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 16, M = 6e6 + 10, P = 1e9 + 7; int n, m, L = 1, lim, a[N]; ll ans, t1 = 1, f[M]; ll Qpow(ll x, ll y = P - 2) { ll res = 1; for (; y; y >>= 1, x = x * x % P) if (y & 1) res = res * x % P; return res; } ll C(ll t) { ll res = t1; for (ll i = t; i > t - n + 1; --i) (res *= i) %= P; return res; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), L = lcm(L, a[i]), m -= a[i]; for (int i = 1; i < n; ++i) (t1 *= i) %= P; if (m < 0) return puts("0"), 0; lim = min(n * L, m); t1 = Qpow(t1); f[0] = 1; for (int i = 1; i <= n; ++i) { int gi = L / a[i] - 1; int x = a[i]; for (int b = lim; b > lim - x; --b) { ll sum = 0; for (int k = 0; k < gi; ++k) if (b >= k * x) (sum += f[b - k * x]) %= P; else break; for (int j = b; j >= x; j -= x) { sum = (sum - f[j] + ((j >= gi * x) ? f[j - gi * x] : 0ll) + P) % P; (f[j] += sum) %= P; } } } for (int i = 0; i <= lim; ++i) if (!((m - i) % L)) (ans += f[i] * C((m - i) / L + n - 1) % P) %= P; printf("%lld\n", ans); return 0; }
无向图期望碰面步数2
Solution
枚举最终到达哪个点,记为 s,设 f(i,j,t) 为当前 A 在 i,B 在 j,当前 A/B 先手,第一次相遇在 (s,s) 的概率。
f(i,i,t)=[s=i],消元即可,O(n7)。
发现 n 次消元中,不同点仅仅在于边界条件。所以构建 n2−n 行 n2 列的矩阵,消元求出每个 f(a,b,t) 关于 f(1,1,t),f(2,2,t),…,f(n,n,t) 的线性表达,再依次代入 f(i,i,t)=1。O(n6)。
随机4点连通块
Solution
答案 = 1 − 每个连通块大小 ≤3 的概率。
设 f(i,j,k) 为当前共 i 点,形成了 j 个大小为 1 的连通块、k 个大小为 2 的连通块的概率。
每次加入一个点,有如下方案:
- 孤立
- 用 1 条边连接一个大小为 1 的连通块
- 用 2 条边连接两个大小为 1 的连通块
- 用 1 条边连接一个大小为 2 的连通块
- 用 2 条边连接一个大小为 2 的连通块
加入一条边概率就乘以 p,故转移是 O(1) 的。
求和即可,O(n3)。
概率充电器
Solution
首先,根据期望的线性性,答案等于每个点通电概率之和
发现直接求通电的概率不好求,转而求不通电的概率
设 f(u) 为考虑 u 子树,u 不通电的概率,g(u) 为考虑 u 子树外,u 不通电的概率
Pu 的实际意义为,考虑 u 子树以外的部分,fau 不通电的概率。
注意 Pu 式子中若分母为 0,令 Pu=0
g(1)=1
答案:n∑i=11−f(u)g(u)
麻球繁衍
Solution
这题好啊!我连第一句话都没想到。给了题解,DP 转移方程也半天没想明白。
首先所有麻球相互独立,故只需要算出 1 个麻球在 i 天内灭绝的概率
设 f(i) 为 1 个麻球在 i 天内灭绝的概率
枚举这个麻球在第一天分裂成 j 个,然后 j 个麻球都要度过 i−1 天,这是子问题。
答案:f(m)k
水果篮子
Solution
注意到值域很小,所以直接背包即可。
整数抽卡
Solution
设数字全集为 S,Ti 为 i 数字的出现次数
设 f(a,b) 为从 current numbermodm=a,用过数集合为 b 开始,能达成幸运数的概率
(cnblogs 的 LaTeX 好像不支持 rcases……)
答案:f(0,∅)
注意到只有 b 相同时,转移方程具有后效性。而直接暴力全局消元是不可行的。
于是按 b 的大小倒着转移,每转移到一个未消元过的 b,就对所有 f(a,b) 消一次元,再继续做。
复杂度 O(m3210)
Code
#include <bits/stdc++.h> using namespace std; typedef long long ll; const double EPS = 1e-9; double f[30][1025], mat[30][31]; int n, m, S, T[10]; vector<int> S_; char s[1005]; void Gauss() { for (int i = 0; i < m; ++i) { int t = -1; for (int j = i; j < m; ++j) if (fabs(mat[j][i]) > EPS) { t = j; break; } assert(t != -1); swap(mat[t], mat[i]); double r = 1. / mat[i][i]; for (int j = i; j <= m; ++j) mat[i][j] *= r; for (int j = 0; j < m; ++j) if (j != i) { r = mat[j][i]; for (int k = i; k <= m; ++k) mat[j][k] -= mat[i][k] * r; } } } int main() { scanf("%d%d%s", &n, &m, s + 1); for (int i = 1; i <= n; ++i) { int now = s[i] - 48; if (!T[now]++) S |= 1 << now, S_.push_back(now); } vector<int> id; for (int st = S; st; st = (st - 1) & S) id.push_back(st); sort(id.begin(), id.end(), [&](int x, int y) { int a = __builtin_popcount(x); int b = __builtin_popcount(y); if (a != b) return a > b; return x > y; }); for (int b : id) { for (int i = 0; i < m; ++i) for (int j = 0; j <= m; ++j) mat[i][j] = 0; mat[0][0] = 1, mat[0][m] = b == S; for (int a = 1; a < m; ++a) { mat[a][a] = 1; for (int i : S_) { int nxt = (10 * a + i) % m; double t = (double)T[i] / (double)n; if ((b >> i) & 1) mat[a][nxt] -= t; else mat[a][m] += t * f[nxt][b | (1 << i)]; } } Gauss(); for (int a = 0; a < m; ++a) f[a][b] = mat[a][m]; } double ans = 0, r = n - T[0]; for (int i : S_) ans += (double)T[i] / r * f[i % m][1 << i]; printf("%.8lf\n", ans); return 0; }
超长涂色板
Solution
首先把所有 ci 中没出现的颜色 视为同一种颜色,令其编号为 0
设 f(i,j) 为前 i 个格子,第 i 个格子填颜色 j 的方案数
若第 i 个格子被限制不能填颜色 j,f(i,j)=0
直接矩阵快速幂是 r⋅r3logm 的,不能通过。
注意到相邻的 xi 之间的转移矩阵是一样的,而瓶颈在于求 A1,A2,A4,…,所以先将其 r3logm 预处理出来
而一个向量乘以矩阵的复杂度为 n2,所以这时复杂度为 O(r3logm+r⋅r2logm)=O(r3logm)
可以通过。
Code
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int P = 1e9 + 7; struct Mat { ll f[105][105]; int n, m; Mat(int _n = 0, int _m = 0) { memset(f, 0, sizeof(f)); n = _n, m = _m; } Mat operator* (const Mat &o) const { Mat res(n, o.m); for (int i = 1; i <= n; ++i) for (int k = 1; k <= m; ++k) for (int j = 1; j <= o.m; ++j) (res.f[i][j] += f[i][k] * o.f[k][j] % P) %= P; return res; } } A[70], B; int m, r, tot, C[105], l[105]; array<ll, 2> t[105]; ll n; int Move(int q, int p) { ll i = t[q][0], j = t[p][0]; for (ll y = j - i, x = 0; y; y >>= 1, ++x) if (y & 1) B = A[x] * B; for (; p <= r && t[p][0] == j; ++p) B.f[t[p][1]][1] = 0; return p; } int main() { scanf("%lld%d%d", &n, &m, &r); for (int i = 1; i <= r; ++i) scanf("%lld%lld", &t[i][0], &t[i][1]), C[++tot] = t[i][1]; sort(C + 1, C + tot + 1), tot = unique(C + 1, C + tot + 1) - C - 1; for (int i = 1; i <= r; ++i) t[i][1] = lower_bound(C + 1, C + tot + 1, t[i][1]) - C + 1; sort(t + 1, t + r + 1); l[1] = m - tot++, t[0][0] = 1, t[r + 1][0] = n; for (int i = 2; i <= tot; ++i) l[i] = 1; A[0] = Mat(tot, tot); for (int i = 1; i <= tot; ++i) { for (int j = 1; j <= tot; ++j) A[0].f[i][j] = l[i]; --A[0].f[i][i]; } for (ll i = 1, j = 2; j <= n; ++i, j <<= 1) A[i] = A[i - 1] * A[i - 1]; B = Mat(tot, 1); for (int i = 1; i <= tot; ++i) B.f[i][1] = l[i]; int p = 1; for (; p <= r && t[p][0] == 1; ++p) B.f[t[p][1]][1] = 0; for (int s = 0, q; p <= r; p = Move(s, q = p), s = q); if (t[r][0] != n) Move(r, r + 1); ll ans = 0; for (int i = 1; i <= tot; ++i) (ans += B.f[i][1]) %= P; printf("%lld\n", ans); return 0; }
到此为止吧。
本文作者:Laijinyi
本文链接:https://www.cnblogs.com/laijinyi/p/18544620
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步