高斯消元
高斯消元法通常用于求解如下形式的 n 元线性方程组:
⎧⎪
⎪
⎪⎨⎪
⎪
⎪⎩a1,1x1+a2,2x2+⋯+a1,nxn=b1a2,1x1+a2,2x2+⋯+a2,nxn=b2⋯an,1x1+an,2x2+⋯+an,nxn=bn
线性方程组与矩阵的联系
对于一个线性方程组:
⎧⎪
⎪
⎪
⎪⎨⎪
⎪
⎪
⎪⎩a1,1x1+a1,2x2+⋯+a1,nxn=b1a2,1x1+a2,2x2+⋯+a2,nxn=b2⋮am,1x1+am,2x2+⋯+am,nxn=bn
可以表示为下面的矩阵:
⎡⎢
⎢
⎢
⎢
⎢
⎢⎣a1,1a1,2⋯a1,nb1a2,1a2,2⋯a2,nb2⋮⋮⋱⋮⋮am,1am,2⋯am,nbm⎤⎥
⎥
⎥
⎥
⎥
⎥⎦
整个矩阵称为增广矩阵,而没有右边系数的矩阵称为系数矩阵。
考虑把一个线性方程组的 n 个元的解写在一个列向量(仅含一列的矩阵)中,如:
X=⎡⎢
⎢
⎢
⎢⎣x1x2⋮xn⎤⎥
⎥
⎥
⎥⎦
于是可以得到向量方程 AX=B 。
解线性方程组
P3389 【模板】高斯消元法
考虑求解如下方程组:
⎧⎨⎩x1+3x2+4x3=5x1+4x2+7x3=39x1+3x2+2x3=2
把系数写到矩阵里:
⎡⎢⎣134514739322⎤⎥⎦
把未知项的系数单独拿出来考虑:
⎡⎢⎣134147932⎤⎥⎦
如果能够将其化为:
⎡⎢⎣???0??00?⎤⎥⎦
就可以从最后一行逆推上去。设第一行第一列为主元,利用加减消元法配系数消掉后两行的第一列:
⎡⎢⎣1340130−24−34⎤⎥⎦
再以第二行第二列为主元,加减消元得到:
⎡⎢⎣1340130038⎤⎥⎦
即得目标状态,实现时把 b 的那一列一起带入消元即可。
每次选取系数最大的为主元可以有效降低精度误差。
本质就是用初等变换消为一个下三角矩阵。
| #include <bits/stdc++.h> |
| using namespace std; |
| const double eps = 1e-9; |
| const int N = 1e2 + 7; |
| |
| double g[N][N], ans[N]; |
| |
| int n; |
| |
| inline bool Gauss() { |
| for (int i = 1; i <= n; ++i) { |
| int mxp = i; |
| |
| for (int j = i + 1; j <= n; ++j) |
| if (fabs(g[j][i]) > fabs(g[mxp][i])) |
| mxp = j; |
| |
| if (fabs(g[mxp][i]) < eps) |
| return false; |
| |
| if (mxp != i) |
| swap(g[mxp], g[i]); |
| |
| for (int j = i + 1; j <= n; ++j) { |
| double div = g[j][i] / g[i][i]; |
| |
| for (int k = i; k <= n + 1; ++k) |
| g[j][k] -= div * g[i][k]; |
| } |
| } |
| |
| for (int i = n; i; --i) { |
| ans[i] = g[i][n + 1]; |
| |
| for (int j = i + 1; j <= n; ++j) |
| ans[i] -= g[i][j] * ans[j]; |
| |
| ans[i] /= g[i][i]; |
| } |
| |
| return true; |
| } |
| |
| signed main() { |
| scanf("%d", &n); |
| |
| for (int i = 1; i <= n; ++i) |
| for (int j = 1; j <= n + 1; ++j) |
| scanf("%lf", g[i] + j); |
| |
| if (!Gauss()) |
| return puts("No Solution"), 0; |
| |
| for (int i = 1; i <= n; ++i) |
| printf("%.2lf\n", ans[i]); |
| |
| return 0; |
| } |
约旦消元法
其能避免回带来求出答案,也就是要把矩阵变为:
⎡⎢⎣?00?0?0?00??⎤⎥⎦
首先,我们依照朴素的高斯消元不难得到:
⎡⎢⎣????0???00??⎤⎥⎦
观察上下两个矩阵,不难得到消元时同时消去主元上下方的方程即可。
注意一下特殊情况:
| inline bool Gauss() { |
| for (int i = 1; i <= n; ++i) { |
| int mxp = i; |
| |
| for (int j = i + 1; j <= n; ++j) |
| if (fabs(g[j][i]) > fabs(g[mxp][i])) |
| mxpos = j; |
| |
| if (fabs(g[mxp][i]) < eps) |
| return false; |
| |
| if (i != mxp) |
| swap(g[i], g[mxp]); |
| |
| for (int j = 1; j <= n; ++j) |
| if (j != i) { |
| double div = g[j][i] / g[i][i]; |
| |
| for (int k = i + 1; k <= n + 1; ++k) |
| g[j][k] -= g[i][k] * div; |
| } |
| } |
| |
| for (int i = 1; i <= n; ++i) |
| ans[i] = g[i][n + 1] / g[i][i]; |
| |
| return true; |
| } |
解异或方程组
异或方程组是指形如
⎧⎪
⎪
⎪⎨⎪
⎪
⎪⎩a1,1x1⊕a2,2x2⊕⋯⊕a1,nxn=b1a2,1x1⊕a2,2x2⊕⋯⊕a2,nxn=b2⋯an,1x1⊕an,2x2⊕⋯⊕an,nxn=bn
的方程组,其中 ai,j,bi∈{0,1} 。
由于 ⊕ 符合交换律与结合律,故可以按照高斯消元法逐步消元求解。值得注意的是,在消元的时候应使用异或消元而非加减消元,且不需要进行乘除改变系数(因为系数均为 0 和 1 )。
可以用 bitset
优化到 O(n3ω) 。
| inline bool Gauss() { |
| for (int i = 1; i <= n; ++i) { |
| int mxp = i; |
| |
| while (mxpos <= n && !g[mxpos].test(i)) |
| ++mxp; |
| |
| if (mxpos > n) |
| return false; |
| |
| if (i != mxp) |
| swap(g[i], g[mxp]); |
| |
| for (int j = 1; j <= n; ++j) |
| if (j != i && g[j].test(i)) |
| g[j] ^= g[i]; |
| } |
| |
| return true; |
| } |
给出一张无向图,需要给每个点染上黑色或白色。定义一个点合法当且仅当其所有同色邻居数量为偶数,最大化合法点的数量,并构造方案。
n≤200 ,m≤n(n−1)2
可以证明答案始终为 n 。构造就是求解如下异或方程组:
⨁(u,v)∈G(xu⊕xv)=degumod2
这个方程始终有解,否则若出现能将其消为左边没有元而右边为 1 的情况,其说明存在奇数个奇度数的点。而每个奇度数的点要选奇数个邻居在集合中,偶度数点要选偶数个邻居在集合中,从而其导出子图的度数为奇数,矛盾。
| #include <bits/stdc++.h> |
| using namespace std; |
| const int N = 2e2 + 7; |
| |
| bitset<N> g[N]; |
| |
| int n; |
| |
| template <class T = int> |
| inline T read() { |
| char c = getchar(); |
| bool sign = (c == '-'); |
| |
| while (c < '0' || c > '9') |
| c = getchar(), sign |= (c == '-'); |
| |
| T x = 0; |
| |
| while ('0' <= c && c <= '9') |
| x = (x << 1) + (x << 3) + (c & 15), c = getchar(); |
| |
| return sign ? (~x + 1) : x; |
| } |
| |
| inline void Gauss() { |
| for (int i = 1; i <= n; ++i) { |
| if (!g[i].test(i)) { |
| for (int j = i + 1; j <= n; ++j) |
| if (g[j].test(i)) { |
| swap(g[i], g[j]); |
| break; |
| } |
| } |
| |
| for (int j = 1; j <= n; ++j) |
| if (j != i && g[j].test(i)) |
| g[j] ^= g[i]; |
| } |
| } |
| |
| signed main() { |
| n = read(); |
| |
| for (int i = 1; i <= n; ++i) { |
| int d = read(); |
| |
| if (d & 1) |
| g[i].set(i), g[i].set(n + 1); |
| |
| while (d--) |
| g[i].set(read()); |
| } |
| |
| Gauss(); |
| int cnt = 0; |
| |
| for (int i = 1; i <= n; ++i) |
| if (g[i].test(n + 1)) |
| ++cnt; |
| |
| printf("%d\n", cnt); |
| |
| for (int i = 1; i <= n; ++i) |
| if (g[i].test(n + 1)) |
| printf("%d ", i); |
| |
| return 0; |
| } |
矩阵求逆
P4783 【模板】矩阵求逆
对于矩阵 A ,若存在矩阵 A−1 使得 A×A−1=A−1×A=I ,则称矩阵 A 可逆,A−1 为其逆矩阵。
给出 n 阶方阵 A ,求解其逆矩阵的方法如下:
- 构造一个 n×2n 的矩阵 (A,In)
- 用高斯消元法将其化简为最简形 (In,A−1) ,即可得到 A 的逆矩阵 A−1 。
- 如果最终最简形的左半部分不是单位矩阵 In ,则矩阵 A 不可逆。
| inline bool Gauss() { |
| for (int i = 1; i <= n; ++i) |
| g[i][i + n] = 1; |
| |
| for (int i = 1; i <= n; ++i) { |
| int mxp = i; |
| |
| while (mxp <= n && !g[mxp][i]) |
| ++mxp; |
| |
| if (mxp > n) |
| return false; |
| |
| if (mxp != i) |
| swap(g[i], g[mxp]); |
| |
| int inv = mi(g[i][i], Mod - 2); |
| |
| for (int j = 1; j <= n; ++j) |
| if (j != i) { |
| int div = 1ll * g[j][i] * inv % Mod; |
| |
| for (int k = i; k <= n * 2; ++k) |
| g[j][k] = dec(g[j][k], 1ll * div * g[i][k] % Mod); |
| } |
| } |
| |
| for (int i = 1; i <= n; ++i) { |
| int inv = mi(g[i][i], Mod - 2); |
| |
| for (int j = n + 1; j <= n * 2; ++j) |
| g[i][j] = 1ll * g[i][j] * inv % Mod; |
| } |
| |
| return true; |
| } |
行列式求值
P7112 【模板】行列式求值
考虑一个情况,当一个矩阵任意一个位置出现 0 ,其对行列式的影响非常大(直接没有贡献了)。
我们利用上面的一些性质显然是可以让矩阵不断变化出现 0 的。考虑将矩阵一行(列)消成只有最后一个元素非 0 该怎么做。也就是说:
⎡⎢
⎢
⎢
⎢
⎢⎣a1,1a1,2⋯a1,na2,1a2,2⋯a2,n⋮⋮⋱⋮an,1an,2⋯an,n⎤⎥
⎥
⎥
⎥
⎥⎦⇒⎡⎢
⎢
⎢
⎢
⎢⎣a1,1a1,2⋯a1,na2,1a2,2⋯a2,n⋮⋮⋱⋮00⋯an,n⎤⎥
⎥
⎥
⎥
⎥⎦
对于 1∼n−1 列中的第 i 列,我们只要让第 i 列整列加上第 n 列的 −an,ian,n 倍即可在 detA 不变的情况下消掉点 n−1 个元素
运用行列式展开发现 detA=an,n×An,n
去掉 an,n 所在的行和列,继续消元,则发现 detA=an−1,n−1×An−1,n−1
以此类推,如果我们一直对当前行列式消元,取最末(右下角)位的指和其余子式,余子式作为新行列式重新做。
一直递归下去做,我们发现最后我们得到一个下三角行列式。
我们就会发现这样一个矩阵的行列式是其对角线所有元素的乘积,即 ∏ni=1ai,i 。
| inline int Gauss(int n) { |
| int res = 1; |
| |
| for (int i = 1; i <= n; ++i) |
| for (int j = i + 1; j <= n; ++j) { |
| while (g[i][i]) { |
| int div = g[j][i] / g[i][i]; |
| |
| for (int k = i; k <= n; ++k) |
| g[j][k] = dec(g[j][k], 1ll * g[i][k] * div % Mod); |
| |
| swap(g[i], g[j]), res = Mod - res; |
| } |
| |
| swap(g[i], g[j]), res = Mod - res; |
| } |
| |
| for (int i = 1; i <= n; ++i) |
| res = 1ll * res * g[i][i] % Mod; |
| |
| return res; |
| } |
band-matrix
长这个样子:

空白部分都为 0 ,橙色部分可以为任何数,这样中间就形成了一个宽度大约为 d 的带。
可以发现任意一个 i 满足从 (i,i) 向右或向下拓展都有不超过 d−1 个非零数字,即很多位置根本不需要消。
具体地,假设现在要消第 i 列,那么从第 i 行开始往下枚举 d−1 行,每行往右消 d 个数字即可,最后仍能得到一个上三角矩阵。
与普通高斯消元有点不一样的地方在于当主元为 0 的时候的处理方法。在 band-matrix 中,若直接交换行会破坏 band-matrix 。注意到每次交换完后交换的行右边最多多出 d 个数,于是每次往右消元 2d 个数即可。
时间复杂度 O(nd2) 。
| inline bool Gauss(int n, int band) { |
| for (int i = 1; i <= n; ++i) { |
| int mxpos = i; |
| |
| while (mxpos <= min(i + band, n) && fabs(g[mxpos][i]) < eps) |
| ++mxpos; |
| |
| if (mxpos > min(i + band, n)) |
| return false; |
| |
| if (i != mxpos) |
| swap(g[i], g[mxpos]); |
| |
| for (int j = i + 1; j <= min(i + band, n); ++j) { |
| double div = g[j][i] / g[i][i]; |
| |
| for (int k = i; k <= min(i + 2 * band, n); ++k) |
| g[j][k] -= div * g[i][k]; |
| |
| g[j][n + 1] -= div * g[i][n + 1]; |
| } |
| } |
| |
| for (int i = n; i; --i) { |
| ans[i] = g[i][n + 1]; |
| |
| for (int j = i + 1; j <= min(i + 2 * band, n); ++j) |
| ans[i] -= g[i][j] * ans[j]; |
| |
| ans[i] /= g[i][i]; |
| } |
| |
| return true; |
| } |
还有一种解决主元为 0 的方法,普通高消是交换行,这里只要交换列就可以保持 band-matrix 的性质了,时间复杂度也是 O(nd2) 。
| inline void Gauss(int n, int band) { |
| iota(id + 1, id + 1 + n, 1); |
| |
| for (int i = 1; i <= n; ++i) { |
| if (!g[i][i]) { |
| for (int j = i + 1; j <= min(n, i + band); ++j) |
| if (g[i][j]) { |
| for (int k = 1; k <= n; ++k) |
| swap(g[k][i], g[k][j]); |
| |
| swap(id[i], id[j]); |
| break; |
| } |
| } |
| |
| int Inv = mi(g[i][i], Mod - 2); |
| |
| for (int j = i + 1; j <= n; ++j) { |
| int div = 1ll * g[j][i] * Inv % Mod; |
| |
| for (int k = i; k <= min(n, i + band); ++k) |
| g[j][k] = dec(g[j][k], 1ll * g[i][k] * div % Mod); |
| |
| g[j][n + 1] = dec(g[j][n + 1], 1ll * g[i][n + 1] * div % Mod); |
| } |
| } |
| |
| for (int i = n; i; --i) { |
| ans[id[i]] = g[i][n + 1]; |
| |
| for (int j = i + 1; j <= min(n, i + band); ++j) |
| ans[id[i]] = dec(ans[i], 1ll * ans[id[j]] * g[i][j] % Mod); |
| |
| ans[id[i]] = 1ll * ans[id[i]] * mi(g[i][i], Mod - 2) % Mod; |
| } |
| } |
n 行 m 列的矩阵,现在在 (x,y),每次等概率向左、右、下走或原地不动,但不能走出去,求走到最后一行期望的步数。
n,m≤103
记 fi,j 表示机器人在 (i,j) 时走到最后一行的期望步数,则:
m=1 时有(省略第二维):
fi=1+12(fi+1+fi)
即:
fi=fi+1+2
m>1 时有:
fi,j=1+⎧⎪
⎪
⎪
⎪
⎪
⎪⎨⎪
⎪
⎪
⎪
⎪
⎪⎩13(fi,j+fi+1,j+fi,j+1),j=114(fi,j+fi+1,j+fi,j−1+fi,j+1),1<j<m13(fi,j+fi+1,j+fi,j−1),j=m
注意到这是一个 d=2 的 band-matrix ,直接使用 band-matrix 消元即可,时间复杂度 O(nmd2) 。
| #include <bits/stdc++.h> |
| using namespace std; |
| const double eps = 1e-12; |
| const int N = 1e3 + 7; |
| |
| double g[N][N], ans[N][N]; |
| |
| int n, m, x, y; |
| |
| inline void Gauss(int n, int band, double *ans) { |
| for (int i = 1; i <= n; ++i) { |
| int mxpos = i; |
| |
| while (mxpos <= min(i + band, n) && fabs(g[mxpos][i]) < eps) |
| ++mxpos; |
| |
| if (mxpos > min(i + band, n)) |
| continue; |
| |
| if (i != mxpos) |
| swap(g[i], g[mxpos]); |
| |
| for (int j = i + 1; j <= min(i + band, n); ++j) { |
| double div = g[j][i] / g[i][i]; |
| |
| for (int k = i; k <= min(i + 2 * band, n); ++k) |
| g[j][k] -= div * g[i][k]; |
| |
| g[j][n + 1] -= div * g[i][n + 1]; |
| } |
| } |
| |
| for (int i = n; i; --i) { |
| ans[i] = g[i][n + 1]; |
| |
| for (int j = i + 1; j <= min(i + 2 * band, n); ++j) |
| ans[i] -= g[i][j] * ans[j]; |
| |
| ans[i] /= g[i][i]; |
| } |
| } |
| |
| signed main() { |
| scanf("%d%d%d%d", &n, &m, &x, &y); |
| |
| if (m == 1) |
| return printf("%.10lf\n", 2.0 * (n - x)), 0; |
| |
| for (int i = n - 1; i; --i) { |
| g[1][1] = 2, g[1][2] = -1, g[1][m + 1] = 3 + ans[i + 1][1]; |
| |
| for (int j = 2; j < m; ++j) |
| g[j][j] = 3, g[j][j - 1] = g[j][j + 1] = -1, g[j][m + 1] = 4 + ans[i + 1][j]; |
| |
| g[m][m] = 2, g[m][m - 1] = -1, g[m][m + 1] = 3 + ans[i + 1][m]; |
| Gauss(m, 1, ans[i]); |
| } |
| |
| printf("%.10lf\n", ans[x][y]); |
| return 0; |
| } |
平面直角坐标系上有一个点,一开始在 (0,0) ,每秒钟这个点都会随机移动,如果它在 (x,y) ,下一秒:
- 在 (x−1,y) 的概率是 p1
- 在 (x,y−1) 的概率是 p2
- 在 (x+1,y) 的概率是 p3
- 在 (x,y+1) 的概率是 p4
保证 p1+p2+p3+p4=1 ,求该点移动至距离原点距离为大于 R 的点的期望步数
0≤R≤50
把所有满足 i2+j2≤R2 的点依次编号,显然有 O(R2) 个点。
设 fid(i,j) 表示 (i,j) 走出圆的期望步数,(i,j) 只要转移到 (i,j−1),(i−1,j),(i,j+1),(i+1,j) 。因为是依次编号,所以建出来的矩阵带宽 ≤2R+1 。套用 band-matrix ,可以做到 O(R2d2)=O(R4) 。
| #include <bits/stdc++.h> |
| typedef long long ll; |
| using namespace std; |
| const int Mod = 1e9 + 7; |
| const int R = 5e1 + 7, SIZE = 8e3 + 7; |
| |
| vector<pair<int, int> > Pos; |
| |
| int g[SIZE][SIZE]; |
| int id[R << 1][R << 1]; |
| int ans[SIZE]; |
| |
| int r, p1, p2, p3, p4; |
| |
| template <class T = int> |
| inline T read() { |
| char c = getchar(); |
| bool sign = c == '-'; |
| |
| while (c < '0' || c > '9') |
| c = getchar(), sign |= c == '-'; |
| |
| T x = 0; |
| |
| while ('0' <= c && c <= '9') |
| x = (x << 1) + (x << 3) + (c & 15), c = getchar(); |
| |
| return sign ? (~x + 1) : x; |
| } |
| |
| inline int add(int x, int y) { |
| x += y; |
| |
| if (x >= Mod) |
| x -= Mod; |
| |
| return x; |
| } |
| |
| inline int dec(int x, int y) { |
| x -= y; |
| |
| if (x < 0) |
| x += Mod; |
| |
| return x; |
| } |
| |
| inline int mi(int g, int b) { |
| int res = 1; |
| |
| for (; b; b >>= 1, g = 1ll * g * g % Mod) |
| if (b & 1) |
| res = 1ll * res * g % Mod; |
| |
| return res; |
| } |
| |
| inline int getid(int x, int y) { |
| return id[x + R][y + R]; |
| } |
| |
| inline void Gauss(int n, int band) { |
| for (int i = 1; i <= n; ++i) { |
| int mxpos = i; |
| |
| while (mxpos <= min(i + band, n) && !g[mxpos][i]) |
| ++mxpos; |
| |
| if (mxpos > min(i + band, n)) |
| continue; |
| |
| if (i != mxpos) |
| swap(g[i], g[mxpos]); |
| |
| int inv = mi(g[i][i], Mod - 2); |
| |
| for (int j = i + 1; j <= min(i + band, n); ++j) { |
| int div = 1ll * g[j][i] * inv % Mod; |
| |
| for (int k = i; k <= min(i + 2 * band, n); ++k) |
| g[j][k] = dec(g[j][k], 1ll * div * g[i][k] % Mod); |
| |
| g[j][n + 1] = dec(g[j][n + 1], 1ll * div * g[i][n + 1] % Mod); |
| } |
| } |
| |
| for (int i = n; i; --i) { |
| ans[i] = g[i][n + 1]; |
| |
| for (int j = i + 1; j <= min(i + 2 * band, n); ++j) |
| ans[i] = dec(ans[i], 1ll * g[i][j] * ans[j] % Mod); |
| |
| ans[i] = 1ll * ans[i] * mi(g[i][i], Mod - 2) % Mod; |
| } |
| } |
| |
| signed main() { |
| r = read(), p1 = read(), p2 = read(), p3 = read(), p4 = read(); |
| int invsum = mi(add(add(p1, p2), add(p3, p4)), Mod - 2); |
| p1 = 1ll * p1 * invsum % Mod, p2 = 1ll * p2 * invsum % Mod; |
| p3 = 1ll * p3 * invsum % Mod, p4 = 1ll * p4 * invsum % Mod; |
| |
| for (int i = -r; i <= r; ++i) |
| for (int j = -r; j <= r; ++j) |
| if (i * i + j * j <= r * r) |
| Pos.emplace_back(make_pair(i, j)), id[i + R][j + R] = Pos.size(); |
| |
| int n = Pos.size(), band = 0; |
| |
| for (auto it : Pos) { |
| int x = it.first, y = it.second; |
| g[getid(x, y)][getid(x, y)] = g[getid(x, y)][n + 1] = 1; |
| |
| if (getid(x - 1, y)) { |
| g[getid(x, y)][getid(x - 1, y)] = Mod - p1; |
| band = max(band, abs(getid(x, y) - getid(x - 1, y))); |
| } |
| |
| if (getid(x, y - 1)) { |
| g[getid(x, y)][getid(x, y - 1)] = Mod - p2; |
| band = max(band, abs(getid(x, y) - getid(x, y - 1))); |
| } |
| |
| if (getid(x + 1, y)) { |
| g[getid(x, y)][getid(x + 1, y)] = Mod - p3; |
| band = max(band, abs(getid(x, y) - getid(x + 1, y))); |
| } |
| |
| if (getid(x, y + 1)) { |
| g[getid(x, y)][getid(x, y + 1)] = Mod - p4; |
| band = max(band, abs(getid(x, y) - getid(x, y + 1))); |
| } |
| } |
| |
| Gauss(n, band); |
| printf("%d", ans[getid(0, 0)]); |
| return 0; |
| } |
你有 p 滴血量,血量上限为 n 。每轮操作如下:
- 先以 1m+1 的概率增加 1 滴血,满血时则概率为 0 。
- k 次判定,每次以 1m+1 的概率减少一滴血,死了则概率为 0 。
求期望几轮死亡。
n≤1500 ,m,k≤109
设 fi 表示血量为 i 时期望多少轮结束,则:
fi=1+min(i,k)∑j=0(mm+1fi−j+1m+1fi−j+1)×(kj)(1m+1)j(mm+1)k−j
注意一下 i=n 时的情况即可。
观察到 fi 只与 f0∼i+1 有关,所以矩阵应该是一个类似下三角矩阵的东西。因为我们高斯消元的时候是拿自己这行去减下面的,所以每一行中只有 2 个系数要去和下面的相减,时间复杂度就可以做到 O(n2) 了。
| #include <bits/stdc++.h> |
| using namespace std; |
| const int Mod = 1e9 + 7; |
| const int N = 1.5e3 + 7; |
| |
| int g[N][N]; |
| int inv[N], d[N], id[N], ans[N]; |
| |
| int n, p, m, k; |
| |
| template <class T = int> |
| inline T read() { |
| char c = getchar(); |
| bool sign = (c == '-'); |
| |
| while (c < '0' || c > '9') |
| c = getchar(), sign |= (c == '-'); |
| |
| T x = 0; |
| |
| while ('0' <= c && c <= '9') |
| x = (x << 1) + (x << 3) + (c & 15), c = getchar(); |
| |
| return sign ? (~x + 1) : x; |
| } |
| |
| inline int add(int x, int y) { |
| x += y; |
| |
| if (x >= Mod) |
| x -= Mod; |
| |
| return x; |
| } |
| |
| inline int dec(int x, int y) { |
| x -= y; |
| |
| if (x < 0) |
| x += Mod; |
| |
| return x; |
| } |
| |
| inline int mi(int a, int b) { |
| int res = 1; |
| |
| for (; b; b >>= 1, a = 1ll * a * a % Mod) |
| if (b & 1) |
| res = 1ll * res * a % Mod; |
| |
| return res; |
| } |
| |
| inline void prework() { |
| inv[0] = inv[1] = 1; |
| |
| for (int i = 2; i < N; ++i) |
| inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod; |
| } |
| |
| inline void Gauss(int n, int band) { |
| iota(id + 1, id + 1 + n, 1); |
| |
| for (int i = 1; i <= n; ++i) { |
| if (!g[i][i]) { |
| for (int j = i + 1; j <= min(n, i + band); ++j) |
| if (g[i][j]) { |
| for (int k = 1; k <= n; ++k) |
| swap(g[k][i], g[k][j]); |
| |
| swap(id[i], id[j]); |
| break; |
| } |
| } |
| |
| int Inv = mi(g[i][i], Mod - 2); |
| |
| for (int j = i + 1; j <= n; ++j) { |
| int div = 1ll * g[j][i] * Inv % Mod; |
| |
| for (int k = i; k <= min(n, i + band); ++k) |
| g[j][k] = dec(g[j][k], 1ll * g[i][k] * div % Mod); |
| |
| g[j][n + 1] = dec(g[j][n + 1], 1ll * g[i][n + 1] * div % Mod); |
| } |
| } |
| |
| for (int i = n; i; --i) { |
| ans[id[i]] = g[i][n + 1]; |
| |
| for (int j = i + 1; j <= min(n, i + band); ++j) |
| ans[id[i]] = dec(ans[i], 1ll * ans[id[j]] * g[i][j] % Mod); |
| |
| ans[id[i]] = 1ll * ans[id[i]] * mi(g[i][i], Mod - 2) % Mod; |
| } |
| } |
| |
| signed main() { |
| prework(); |
| int T = read(); |
| |
| while (T--) { |
| n = read(), p = read(), m = read(), k = read(); |
| |
| if (!k) { |
| puts("-1"); |
| continue; |
| } else if (!m) { |
| if (k == 1) |
| puts("-1"); |
| else |
| printf("%d\n", (p + k - 2 - (p == n)) / (k - 1)); |
| |
| continue; |
| } |
| |
| d[0] = 1ll * mi(m, k) * mi(mi(m + 1, k), Mod - 2) % Mod; |
| |
| for (int i = 1, invm = mi(m, Mod - 2); i <= min(n, k); ++i) |
| d[i] = 1ll * d[i - 1] * invm % Mod * (k - i + 1) % Mod * inv[i] % Mod; |
| |
| memset(g, 0, sizeof(g)); |
| |
| for (int i = 1, invm1 = mi(m + 1, Mod - 2); i < n; ++i) { |
| g[i][n + 1] = g[i][i] = 1; |
| |
| for (int j = 0; j <= min(i, k); ++j) { |
| g[i][i - j] = dec(g[i][i - j], 1ll * dec(1, invm1) * d[j] % Mod); |
| g[i][i - j + 1] = dec(g[i][i - j + 1], 1ll * invm1 * d[j] % Mod); |
| } |
| } |
| |
| g[n][n + 1] = g[n][n] = 1; |
| |
| for (int i = 0; i <= min(n, k); ++i) |
| g[n][n - i] = dec(g[n][n - i], d[i]); |
| |
| Gauss(n, 1); |
| printf("%d\n", ans[p]); |
| } |
| |
| return 0; |
| } |
有一个宽度为 w 高度为 h 的方格纸, w×h 的格子中,有一些是空的,有一些是洞,有一些是障碍物。从第一行的空的格子中随机选一个放置一个球,向上下左右移动的概率比为 pu:pd:pl:pr ,不能移动到有障碍物的格子上。对于每个洞,输出落入该洞的概率。
2≤20,h≤104,pu+pd+pl+pr=100 。
和上面比较类似。
| #include <bits/stdc++.h> |
| using namespace std; |
| const double eps = 1e-12; |
| const int dx[] = {-1, 1, 0, 0}; |
| const int dy[] = {0, 0, -1, 1}; |
| const int N = 1e4 + 7, M = 2e1 + 7; |
| |
| double g[N * M][M << 1], ans[N * M]; |
| int id[N][M]; |
| char str[N][M]; |
| |
| int n, m, p[4], tot; |
| |
| inline bool check(int x, int y) { |
| return x && x <= n && y && y <= m; |
| } |
| |
| inline void Gauss(int n, int band) { |
| for (int i = 1; i <= n; ++i) { |
| if (fabs(g[i][i]) < eps) |
| continue; |
| |
| for (int j = i + 1; j <= min(i + band, n); ++j) |
| g[i][j] /= g[i][i]; |
| |
| ans[i] /= g[i][i], g[i][i] = 1; |
| |
| for (int j = i + 1; j <= min(i + band, n); ++j) { |
| double div = g[j][i] / g[i][i]; |
| |
| for (int k = i; k <= min(i + band, n); ++k) |
| g[j][k] -= g[i][k] * div; |
| |
| ans[j] -= ans[i] * div; |
| } |
| } |
| |
| for (int i = n; i; --i) |
| for (int j = i + 1; j <= min(i + band, n); ++j) |
| ans[i] -= g[i][j] * ans[j]; |
| } |
| |
| signed main() { |
| scanf("%d%d", &m, &n); |
| |
| for (int i = 0; i < 4; ++i) |
| scanf("%d", p + i); |
| |
| for (int i = 1; i <= n; ++i) { |
| scanf("%s", str[i] + 1); |
| |
| for (int j = 1; j <= m; ++j) |
| if (str[i][j] != 'X') |
| id[i][j] = ++tot; |
| } |
| |
| int sum = m - count(id[1] + 1, id[1] + 1 + m, 0); |
| |
| for (int i = 1; i <= n; ++i) |
| for (int j = 1; j <= m; ++j) { |
| if (!id[i][j]) |
| continue; |
| |
| if (i == 1) |
| ans[id[i][j]] = 1.0 / sum; |
| |
| if (str[i][j] == 'T') |
| continue; |
| |
| g[id[i][j]][id[i][j]] = 1; |
| int base = 100; |
| |
| for (int k = 0; k < 4; ++k) { |
| int x = i + dx[k], y = j + dy[k]; |
| |
| if (!check(x, y) || !id[x][y]) |
| base -= p[k]; |
| } |
| |
| for (int k = 0; k < 4; ++k) { |
| int x = i + dx[k], y = j + dy[k]; |
| |
| if (check(x, y) && id[x][y]) |
| g[id[x][y]][id[i][j]] = -1.0 * p[k] / base; |
| } |
| } |
| |
| Gauss(tot, m); |
| |
| for (int i = 1; i <= n; ++i) |
| for (int j = 1; j <= m; ++j) |
| if (str[i][j] == 'T') |
| printf("%.9lf\n", ans[id[i][j]]); |
| |
| return 0; |
| } |
树上高消
若在树上进行高斯消元,往往一个叶子和其父亲的值呈一次函数关系。
那么类似数学归纳法地向上递推,可以发现所有点的值和根的值都是一次函数关系。
此时会发现所有值之和为定值,或者某些点(如根、叶子)的值容易求,那么就可以递推优化到 O(n) 。
给定一棵有根树,q 次询问从根走到 S 中的所有点至少一次的期望步数。
n≤18 ,q≤5000
求经过 S 里所有元素的期望时间,即到达 S 中最后一个点的期望步数( max ),那么可以转化为枚举 S 的子集 T ,求到达 T 中第一个元素的期望时间( min )。
考虑 Min-Max 容斥,设 fu,S 表示 u 第一次走到 S 中的点的期望步数,du 表示 u 的度数,则:
fu,S=ffau,S+∑v∈son(u)fv,Sdu+1(x∉S)fu,S=0(x∈S)
考虑将每个点的值都写作 fu,S=ku×ffau,S+bu 的形式,记:
Ku=∑v∈son(u)kv,Bu=∑v∈son(u)bv
则得到:
fu,S=1du−Ku×ffau,S+du+Budu−Ku
即:
ku=1du−Ku,bu=du+Budu−Ku
答案即为 ∑T⊆S(−1)|T|+1fr,T ,不难用高维前缀和预处理后 O(1) 查询。时间复杂度 O(n2n+q) 。
| #include <bits/stdc++.h> |
| using namespace std; |
| const int Mod = 998244353; |
| const int N = 19; |
| |
| struct Graph { |
| vector<int> e[N]; |
| |
| inline void insert(int u, int v) { |
| e[u].emplace_back(v); |
| } |
| } G; |
| |
| int f[1 << N], g[1 << N]; |
| int deg[N]; |
| |
| int n, q, r; |
| |
| template <class T = int> |
| inline T read() { |
| char c = getchar(); |
| bool sign = (c == '-'); |
| |
| while (c < '0' || c > '9') |
| c = getchar(), sign |= (c == '-'); |
| |
| T x = 0; |
| |
| while ('0' <= c && c <= '9') |
| x = (x << 1) + (x << 3) + (c & 15), c = getchar(); |
| |
| return sign ? (~x + 1) : x; |
| } |
| |
| inline int add(int x, int y) { |
| x += y; |
| |
| if (x >= Mod) |
| x -= Mod; |
| |
| return x; |
| } |
| |
| inline int dec(int x, int y) { |
| x -= y; |
| |
| if (x < 0) |
| x += Mod; |
| |
| return x; |
| } |
| |
| inline int mi(int a, int b) { |
| int res = 1; |
| |
| for (; b; b >>= 1, a = 1ll * a * a % Mod) |
| if (b & 1) |
| res = 1ll * res * a % Mod; |
| |
| return res; |
| } |
| |
| inline int sgn(int n) { |
| return n & 1 ? Mod - 1 : 1; |
| } |
| |
| struct Node { |
| int k, b; |
| |
| inline Node(const int _b = 0) : k(0), b(_b) {} |
| |
| inline Node(const int _k, const int _b) : k(_k), b(_b) {} |
| |
| inline Node operator + (const Node &rhs) const { |
| return Node(add(k, rhs.k), add(b, rhs.b)); |
| } |
| } nd[N]; |
| |
| void dfs(int u, int f, int state) { |
| Node res = 0; |
| |
| for (int v : G.e[u]) |
| if (v != f) |
| dfs(v, u, state), res = res + nd[v]; |
| |
| if (~state >> u & 1) { |
| nd[u].k = mi(dec(deg[u], res.k), Mod - 2); |
| nd[u].b = 1ll * add(deg[u], res.b) * nd[u].k % Mod; |
| } else |
| nd[u] = 0; |
| } |
| |
| signed main() { |
| n = read(), q = read(), r = read() - 1; |
| |
| for (int i = 1; i < n; ++i) { |
| int u = read() - 1, v = read() - 1; |
| G.insert(u, v), G.insert(v, u); |
| ++deg[u], ++deg[v]; |
| } |
| |
| for (int i = 1; i < (1 << n); ++i) |
| dfs(r, -1, i), f[i] = 1ll * nd[r].b * sgn(__builtin_popcount(i) + 1) % Mod; |
| |
| for (int i = 0; i < n; ++i) |
| for (int j = 0; j < (1 << n); ++j) |
| if (j >> i & 1) |
| f[j] = add(f[j], f[j ^ (1 << i)]); |
| |
| while (q--) { |
| int k = read(), state = 0; |
| |
| while (k--) |
| state |= 1 << (read() - 1); |
| |
| printf("%d\n", f[state]); |
| } |
| |
| return 0; |
| } |
有一棵树,点权 a1∼n 构成了一个 0∼n−1 的排列,并且满足 a1=0 。
初始时,每个节点各有一只猴子,每一秒第 i 个点上的猴子会有 p(i) 的概率跳到父亲,1−p(i)sizi 的概率跳到子树内的任意点,其中 p(i)={0i=1ain2≤i≤n 。
初始时将所有点的权值变为 (ai+x)modn ,其中 x 为自行选定任意非负整数。
记第 i 秒成功跳到父亲的猴子数量为 gi ,幸福指数被定义为 g0∼+∞ 的平均数。选取适当的 x ,求幸福指数期望的最大值。
n≤5×105 ,节点 i 的父亲从 1∼i−1 中等概率选取
先考虑初始操作 x=0 的情况。可以发现每只猴子对答案的贡献是独立的,进一步发现每只猴子的初始位置是不重要的。因为时间无穷,因此一只猴子一定会跳到根,并可以忽略跳到根之前的贡献。下面讨论从根开始跳的答案。
记 pi=p(i),qi=1−pisizi ,设猴子处于 u 的概率为 fu ,则:
fu=⎛⎝∑w∈anc(u)∪{u}qwfw⎞⎠+⎛⎝∑v∈son(u)pvfv⎞⎠∑fu=1
暴力高消可以做到 O(n3) ,但是没有利用树上高消的性质。
枚举祖先并不好树上高消,记 Fu=∑w∈anc(u)qwfw ,则 fu=1qu(Fu−Ffau) ,带入得到:
1qu(Fu−Ffau)=Fu+∑v∈son(u)pvqv(Fv−Fu)(1qu−1+∑v∈son(u)pvqv)Fu=1quFfau+∑v∈son(u)pvqvFv
记:
Au=1qu−1+∑v∈son(u)pvqvBu=1quCu=puqu(1)(2)(3)
则:
AuFu=BuFfau+∑v∈son(u)CvFv
考虑设 Fu=kuFfau+bu ,则:
Au(kuFfau+bu)=BuFfau+∑v∈son(u)Cv(kvFu+bv)(Au−∑v∈son(u)Cvkv)Fu=BuFfau+∑v∈son(u)Cvbv(4)(5)
于是可以 O(n) 高消求解 x=0 时幸福指数的期望 ∑pifi 。
考虑 x≠0 的情况,此时一定存在一个点 i 满足 p(i)=0 ,也就是说跳到 i 子树内后就跳不出来了。于是对于每个点的子树做一遍树上高消即可。
时间复杂度 O(∑sizi) ,因为树的形态随机,因此时间复杂度 O(nlogn) 。
| #include <bits/stdc++.h> |
| typedef long double ld; |
| using namespace std; |
| const int N = 5e5 + 7; |
| |
| struct Graph { |
| vector<int> e[N]; |
| |
| inline void insert(int u, int v) { |
| e[u].emplace_back(v); |
| } |
| } G; |
| |
| struct Node { |
| ld k, b; |
| |
| inline Node() {} |
| |
| inline Node(ld _b) : b(_b) {} |
| |
| inline Node(ld _k, ld _b) : k(_k), b(_b) {} |
| |
| inline Node operator + (Node rhs) { |
| return Node(k + rhs.k, b + rhs.b); |
| } |
| |
| inline Node operator - (Node rhs) { |
| return Node(k - rhs.k, b - rhs.b); |
| } |
| |
| inline Node operator * (ld x) { |
| return Node(k * x, b * x); |
| } |
| |
| inline Node operator / (ld x) { |
| return Node(k / x, b / x); |
| } |
| } nd[N], F[N]; |
| |
| ld p[N], q[N], A[N], B[N], C[N], f[N]; |
| int fa[N], a[N], siz[N], in[N], out[N], id[N]; |
| |
| int n, dfstime; |
| |
| void dfs(int u) { |
| siz[u] = 1, id[in[u] = ++dfstime] = u; |
| |
| for (int v : G.e[u]) |
| dfs(v), siz[u] += siz[v]; |
| |
| out[u] = dfstime; |
| } |
| |
| inline ld solve(int r) { |
| for (int i = in[r]; i <= out[r]; ++i) { |
| int u = id[i]; |
| p[u] = (ld)((a[u] + n - a[r]) % n) / n; |
| q[u] = (1 - p[u]) / siz[u]; |
| A[u] = 1 / q[u] - 1, B[u] = 1 / q[u], C[u] = p[u] / q[u]; |
| |
| if (u != r) |
| A[fa[u]] += p[u] / q[u]; |
| } |
| |
| for (int i = out[r]; i > in[r]; --i) { |
| int u = id[i]; |
| nd[u] = Node(B[u], 0); |
| ld div = A[u]; |
| |
| for (int v : G.e[u]) |
| nd[u].b += C[v] * nd[v].b, div -= C[v] * nd[v].k; |
| |
| nd[u] = nd[u] / div; |
| } |
| |
| F[r] = Node(1, 0); |
| Node all = F[r] / q[r]; |
| |
| for (int i = in[r] + 1; i <= out[r]; ++i) { |
| int u = id[i]; |
| F[u] = F[fa[u]] * nd[u].k + nd[u].b; |
| all = all + (F[u] - F[fa[u]]) / q[u]; |
| } |
| |
| f[r] = (1 - all.b) / all.k / q[r]; |
| |
| for (int i = in[r] + 1; i <= out[r]; ++i) { |
| int u = id[i]; |
| Node now = (F[u] - F[fa[u]]) / q[u]; |
| f[u] = now.k * f[r] * q[r] + now.b; |
| } |
| |
| ld res = 0; |
| |
| for (int i = in[r]; i <= out[r]; ++i) { |
| int u = id[i]; |
| res += f[u] * p[u]; |
| } |
| |
| return res; |
| } |
| |
| signed main() { |
| scanf("%d", &n); |
| |
| for (int i = 1; i <= n; ++i) |
| scanf("%d", fa + i), G.insert(fa[i], i); |
| |
| for (int i = 1; i <= n; ++i) |
| scanf("%d", a + i); |
| |
| dfs(1); |
| ld ans = 0; |
| |
| for (int i = 1; i <= n; ++i) |
| ans = max(ans, solve(i)); |
| |
| printf("%.9LF", ans); |
| return 0; |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】