1、HDU - 3364 (https://vjudge.net/problem/HDU-3364)
题意:
有 n n n 盏灯,状态有亮 ( 1 ) (1) (1) 与暗 ( 0 ) (0) (0),有 m m m 个开关,每个开关按下会切换 k i k_i ki 盏灯的状态,每个开关至多按依次。现有 q q q 次询问,每次询问有多少种方案使得灯的状态为 s i s_i si。 ( n , m ≤ 50 , q ≤ 1000 ) (n, m \leq 50, q \leq 1000) (n,m≤50,q≤1000)
解题思路:
将第
i
i
i 个开关控制的灯表示为
n
n
n 维
A
i
A_i
Ai 向量,第
j
j
j 个分量为
1
1
1 表示按下
A
i
A_i
Ai 后切换第
j
j
j 盏灯状态,否则不影响。用
x
i
x_i
xi 表示第
i
i
i 个开关是否按下,
S
S
S 表示末态,则表达式为
∑
i
=
0
m
−
1
x
i
A
i
≡
S
(
m
o
d
2
)
\sum\limits_{i = 0}^{m - 1} x_iA_i \equiv S \pmod{2}
i=0∑m−1xiAi≡S(mod2)
问题转化为求解
S
S
S 是否为向量组
A
i
A_i
Ai 的线性组合(模
2
2
2 意义下),那么将
A
i
A_i
Ai 作为矩阵列向量,即求解
[
A
0
A
1
⋯
A
m
−
2
A
m
−
1
]
[
x
0
x
1
⋮
x
m
−
2
x
m
−
1
]
≡
[
s
0
s
1
⋮
s
n
−
2
s
n
−
1
]
(
m
o
d
2
)
\left[\begin{matrix} \\ ~ \\ A_0 ~ A_1 \cdots A_{m - 2} ~ A_{m - 1} \\ ~ \\ ~\end{matrix}\right] \left[\begin{matrix} x_0 \\ x_1 \\ \vdots \\ x_{m - 2} \\ x_{m - 1} \end{matrix}\right] \equiv \left[\begin{matrix} s_0 \\ s_1 \\ \vdots \\ s_{n - 2} \\ s_{n - 1}\end{matrix}\right] \pmod{2}
⎣⎢⎢⎢⎢⎡ A0 A1⋯Am−2 Am−1 ⎦⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎡x0x1⋮xm−2xm−1⎦⎥⎥⎥⎥⎥⎤≡⎣⎢⎢⎢⎢⎢⎡s0s1⋮sn−2sn−1⎦⎥⎥⎥⎥⎥⎤(mod2)
进行高斯消元,若有解,自由元个数为
x
x
x,答案为
2
x
2^x
2x,模
2
2
2 意义下可用位运算加速,则复杂度为
O
(
q
n
m
)
O(qnm)
O(qnm)。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 5e1 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
struct Gauss{
int piv[maxn];
int solve(bitset<maxn> A[], int n, int m){
int r = 0;
for(int c = 0; c < m && r < n; ++c, ++r){
int mx = r;
for(int i = r; i < n; ++i){
if(A[i][c]) { mx = i; break; }
}
if(!A[mx][c]) { --r; continue; }
piv[r] = c;
swap(A[r], A[mx]);
for(int i = 0; i < n; ++i){
if(i == r || !A[i][c]) continue;
A[i] ^= A[r];
}
}
for(int i = r; i < n; ++i) if(A[i][m]) return -1;
return r;
}
} gauss;
bitset<maxn> A[maxn], B[maxn];
int n, m, q;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int t, cas = 0; cin >> t;
while(t--){
cin >> n >> m;
for(int i = 0; i < n; ++i) A[i].reset();
for(int i = 0; i < m; ++i){
int k; cin >> k;
while(k--){
int tmp; cin >> tmp; --tmp;
A[tmp][i] = 1;
}
}
cin >> q;
cout << "Case " << ++cas << ":\n";
while(q--){
for(int i = 0; i < n; ++i) B[i] = A[i];
for(int i = 0; i < n; ++i){
int tmp; cin >> tmp;
B[i][m] = tmp;
}
int ret = gauss.solve(B, n, m);
cout << (ret == -1 ? 0 : 1ll << (m - ret)) << "\n";
}
}
return 0;
}
2、HDU - 4827 (https://vjudge.net/problem/HDU-4827)
题意:
给定一个有 n n n 个点、 m m m 条边的无向图,现要将结点编号 0 0 0 或 1 1 1,要求每个结点与偶数个邻接点编号相同,输出可行的一种结点编号方案。数据保证有解。 ( n ≤ 1000 , m ≤ 10000 ) (n \leq 1000, m \leq 10000) (n≤1000,m≤10000)
解题思路:
记
x
i
x_i
xi 为结点编号,
d
e
g
[
i
]
deg[i]
deg[i] 为结点
i
i
i 的度,分类讨论一下。
x
u
=
{
0
d
e
g
[
u
]
%
2
=
0
,
⨁
v
x
v
=
1
1
d
e
g
[
u
]
%
2
=
0
,
⨁
v
x
v
=
0
0
/
1
d
e
g
[
u
]
%
2
=
1
,
⨁
v
x
v
=
0
无
解
d
e
g
[
u
]
%
2
=
1
,
⨁
v
x
v
=
1
x_u = \begin{cases} 0 & deg[u]~\%~2 = 0, \bigoplus\limits_{v}x_v = 1 \\ 1 & deg[u]~\%~2 = 0, \bigoplus\limits_{v}x_v = 0 \\ 0 / 1 & deg[u] ~\%~2 = 1, \bigoplus\limits_{v}x_v = 0 \\ 无解 & deg[u] ~\%~2 = 1, \bigoplus\limits_{v}x_v = 1 \end{cases}
xu=⎩⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎧010/1无解deg[u] % 2=0,v⨁xv=1deg[u] % 2=0,v⨁xv=0deg[u] % 2=1,v⨁xv=0deg[u] % 2=1,v⨁xv=1
那么有
⨁
v
x
v
=
{
0
d
e
g
[
u
]
%
2
=
0
x
u
⊕
1
d
e
g
[
u
]
%
2
=
1
\bigoplus\limits_{v}x_v = \begin{cases} 0 & deg[u]~\%~2 = 0 \\ x_u \oplus 1 & deg[u]~\%~2 = 1 \end{cases}
v⨁xv={0xu⊕1deg[u] % 2=0deg[u] % 2=1
对每个结点列出一条方程,高斯消元后确定主元编号,自由元编号统一为
0
0
0 即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 1e3 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
struct Gauss{
int piv[maxn];
int solve(bitset<maxn> A[], int n, int m){
int r = 0;
for(int c = 0; c < m && r < n; ++c, ++r){
int mx = r;
for(int i = r; i < n; ++i){
if(A[i][c]) { mx = i; break; }
}
if(!A[mx][c]) { --r; continue; }
piv[r] = c;
swap(A[r], A[mx]);
for(int i = 0; i < n; ++i){
if(i == r || !A[i][c]) continue;
A[i] ^= A[r];
}
}
for(int i = r; i < n; ++i) if(A[i][m]) return -1;
return r;
}
} gauss;
vector<int> G[maxn];
bitset<maxn> A[maxn];
char ans[maxn];
int deg[maxn], n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int t, cas = 0; cin >> t;
while(t--){
cin >> n >> m;
for(int i = 0; i < n; ++i){
G[i].clear();
A[i].reset();
deg[i] = 0;
}
for(int i = 0; i < m; ++i){
int u, v; cin >> u >> v; --u, --v;
G[u].pb(v), G[v].pb(u);
++deg[u], ++deg[v];
}
for(int i = 0; i < n; ++i){
if(deg[i] & 1) A[i].set(i), A[i].set(n);
for(auto &v : G[i]) A[i].set(v);
}
int rk = gauss.solve(A, n, n);
for(int i = 0; i < n; ++i) ans[i] = '0';
for(int i = 0; i < rk; ++i) ans[gauss.piv[i]] = A[i][n] + '0';
ans[n] = 0;
cout << "Case #" << ++cas << ":\n";
cout << ans << "\n";
}
return 0;
}
3、HDU - 4592 (https://vjudge.net/problem/HDU-4592)
题意:
给定一个 n × n n×n n×n 的方格图,方格初始化为白色,翻转后为黑色。每次操作选定一个方格,将它及曼哈顿距离为 1 1 1 的方格全部翻转颜色。先给定目标方格图,问最少的操作次数,无解则输出 − 1 -1 −1,数据共 m m m 组。 ( n ≤ 8 , m ≤ 1 0 6 ) (n \leq 8, m \leq 10^6) (n≤8,m≤106)
解题思路:
不看数据组数,就是典型的高斯消元,将二维方格图化成一维,问题转化为上述第一题。解方程
A
x
=
b
Ax = b
Ax=b,除去高斯消元的复杂度,还需要枚举自由元的取值(判掉无解的情况,只选取主元并不一定最优,只能说主元所在列向量一定能够线性组合得到
b
b
b),单次复杂度已经很高。考虑到
A
A
A 只有八种,预处理出来发现除
n
=
4
,
5
n = 4, 5
n=4,5 外,
A
A
A 均满秩,且
n
=
4
,
5
n = 4, 5
n=4,5 时
A
A
A 也接近满秩,复杂度瓶颈在高斯消元。
但满秩说明
A
A
A 可逆,
x
=
A
−
1
b
x = A^{-1}b
x=A−1b,预处理出逆矩阵后,单次求解变为一次矩阵乘法复杂度,位运算优化后复杂度不高。对于
n
=
4
,
5
n = 4, 5
n=4,5,直接枚举自由元取值,复杂度也不高。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 65 * 2;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
struct Gauss{
int piv[maxn], fre[maxn], tot;
int solve(bitset<maxn> A[], int n, int m){
int r = 0; tot = 0;
for(int c = 0; c < m && r < n; ++c, ++r){
int mx = r;
for(int i = r; i < n; ++i){
if(A[i][c]) { mx = i; break; }
}
if(!A[mx][c]){
--r, fre[tot++] = c;
continue;
}
piv[r] = c;
swap(A[r], A[mx]);
for(int i = 0; i < n; ++i){
if(i == r || !A[i][c]) continue;
A[i] ^= A[r];
}
}
for(int i = r; i < n; ++i) if(A[i][m]) return -1;
return r;
}
} gauss;
const int xp[] = {-1, 1, 0, 0};
const int yp[] = {0, 0, 1, -1};
bitset<maxn> A[9][maxn], invA[9][maxn], AI[maxn], b, c;
int id[9][maxn][maxn];
int n;
const int maxs = 1e3 + 5;
char buf[maxs], *p1 = buf, *p2 = buf;
inline char fr(){
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, maxs, stdin)) == p1 ? -1 : *p1++;
}
#define gc fr()
inline void read(int &x){
char ch; while(!isdigit(ch = gc)); x = ch ^ 48;
while(isdigit(ch = gc)) x = x * 10 + (ch ^ 48);
}
void init(bitset<maxn> A[], bitset<maxn> invA[], int id[][maxn], int n){
int tot = 0;
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j) id[i][j] = tot++;
}
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
A[id[i][j]][id[i][j]] = 1;
for(int k = 0; k < 4; ++k){
int x = i + xp[k], y = j + yp[k];
if(x < 0 || x >= n || y < 0 || y >= n) continue;
A[id[x][y]][id[i][j]] = 1;
}
}
}
if(n == 4 || n == 5) return;
for(int i = 0; i < tot; ++i) AI[i].reset();
for(int i = 0; i < tot; ++i){
for(int j = 0; j < tot; ++j) AI[i][j] = A[i][j];
AI[i][i + tot] = 1;
}
gauss.solve(AI, tot, tot * 2);
for(int i = 0; i < tot; ++i){
for(int j = 0; j < tot; ++j) invA[i][j] = AI[i][j + tot];
}
}
int main(){
// ios::sync_with_stdio(0); cin.tie(0);
for(int i = 1; i <= 8; ++i) init(A[i], invA[i], id[i], i);
int t; read(t);
while(t--){
read(n);
int tot = n * n;
if(n != 4 && n != 5){
b.reset();
for(int i = 0; i < n; ++i){
int tmp; read(tmp);
for(int j = 0; j < n; ++j){
b[id[n][i][j]] = (tmp >> j) & 1;
}
}
int ret = 0;
for(int i = 0; i < tot; ++i){
ret += (invA[n][i] & b).count() & 1;
}
printf("%d\n", ret);
continue;
}
for(int i = 0; i < tot; ++i) A[0][i] = A[n][i];
for(int i = 0; i < n; ++i){
int tmp; read(tmp);
for(int j = 0; j < n; ++j){
A[0][id[n][i][j]][tot] = (tmp >> j) & 1;
}
}
int rk = gauss.solve(A[0], tot, tot);
if(rk == -1) { printf("-1\n"); continue; }
int ret = inf, lim = 1 << gauss.tot;
b.reset();
for(int i = 0; i < tot; ++i) b[i] = A[0][i][tot];
for(int i = 0; i < lim; ++i){
int tmp = 0; c = b;
for(int j = 0; j < gauss.tot; ++j){
if((~i >> j) & 1) continue;
++tmp;
int p = gauss.fre[j];
for(int k = 0; k < tot; ++k){
if(A[0][k][p]) c.flip(k);
}
}
tmp += c.count();
ret = min(ret, tmp);
}
printf("%d\n", ret);
}
return 0;
}
4、HDU - 5833 (https://vjudge.net/problem/HDU-5833)
题意:
给定长度为 n n n 的数组 a a a,其中 a i a_i ai 不含大于 2000 2000 2000 的质因子,问有多少个非空集合满足集合中元素之积是平方数。 ( n ≤ 300 ) (n \leq 300) (n≤300)
解题思路:
将 a i a_i ai 分解成质因子之积,一个数是平方数当且仅当每种质因子有偶数个,故分奇偶考虑。问题转化为由 n n n 个二进制数,求异或和为 0 0 0 的方案数,列出方程组, A x = 0 Ax = 0 Ax=0 形式,求解个数,设自由元有 r r r 个,答案为 2 r − 1 2^r - 1 2r−1。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 3e2 + 15;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
bitset<maxn> A[maxn];
int prime[2333], vis[2333];
int n, tot;
void init(){
vis[1] = 1;
for(int i = 2; i <= 2000; ++i){
if(!vis[i]) prime[++tot] = i;
for(int j = 1; j <= tot && i * prime[j] <= 2000; ++j){
vis[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
}
struct Gauss{
int piv[maxn], fre[maxn], tot;
int solve(bitset<maxn> A[], int n, int m){
int r = 0; tot = 0;
for(int c = 0; c < m && r < n; ++c, ++r){
int mx = r;
for(int i = r; i < n; ++i){
if(A[i][c]) { mx = i; break; }
}
if(!A[mx][c]) { --r, fre[tot++] = c; continue; }
piv[r] = c;
swap(A[r], A[mx]);
for(int i = 0; i < n; ++i){
if(i == r || !A[i][c]) continue;
A[i] ^= A[r];
}
}
for(int i = r; i < n; ++i) if(A[i][m]) return -1;
return r;
}
} gauss;
ll qpow(ll a, ll b){
ll ret = 1;
while(b){
if(b & 1) ret = ret * a % mod;
a = a * a % mod;
b >>= 1;
}
return ret;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
init();
int t, cas = 0; cin >> t;
while(t--){
cin >> n;
for(int i = 0; i < tot; ++i){
A[i].reset();
}
for(int i = 0; i < n; ++i){
ll tmp; cin >> tmp;
for(int j = 1; j <= tot; ++j){
if(tmp % prime[j]) continue;
int val = 0;
while(tmp % prime[j] == 0) val ^= 1, tmp /= prime[j];
A[j - 1][i] = val;
}
}
int rk = gauss.solve(A, tot, n);
ll ret = (qpow(2, n - rk) - 1 + mod) % mod;
cout << "Case #" << ++cas << ":\n" << ret << "\n";
}
return 0;
}
5、HDU - 3976 (https://vjudge.net/problem/HDU-3976)
题意:
给定一个有 n n n 个结点、 m m m 个电阻的电路图,保证图连通。求电路等效电阻。 ( n ≤ 50 , m ≤ 2000 ) (n \leq 50, m \leq 2000) (n≤50,m≤2000)
解题思路:
定义结点 n n n 电势为 0 0 0,当其他结点电势确定时,整个电路的电流便确定下来, R e q = U e q I e q R_{eq} = \cfrac{U_{eq}}{I_{eq}} Req=IeqUeq。对每个结点列 K C L KCL KCL,则得到 n − 1 n - 1 n−1 个线性无关的方程组,但条件不足,假定结点 1 1 1 电势为 U s U_s Us,那么可以确定所有结点电势,再以此求出等效电流即可。(强推 M I T MIT MIT 线代公开课,目前看了一半,教授讲的线性空间、基等概念很透彻,这道其实也作为应用讲解过)
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 5e2 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
struct Gauss{
#define eps 1e-7
int piv[maxn], fre[maxn], tot;
int solve(double A[][maxn], int n, int m){
int r = 0; tot = 0;
for(int c = 0; c < m && r < n; ++c, ++r){
int mx = r;
for(int i = r + 1; i < n; ++i){
if(fabs(A[i][c]) > fabs(A[mx][c])) mx = i;
}
if(fabs(A[mx][c]) < eps) { --r, fre[tot++] = c; continue; }
piv[r] = c;
for(int j = c; j <= m; ++j) swap(A[r][j], A[mx][j]);
for(int i = 0; i < n; ++i){
if(i == r) continue;
double tmp = A[i][c] / A[r][c];
for(int j = c; j <= m; ++j) A[i][j] -= tmp * A[r][j];
}
}
for(int i = r; i < n; ++i) if(fabs(A[i][m]) > eps) return -1;
for(int i = 0; i < r; ++i){
double tmp = A[i][piv[i]];
for(int j = piv[i]; j <= m; ++j) A[i][j] /= tmp;
}
return r;
}
} gauss;
double A[maxn][maxn];
typedef pair<double, int> pdi;
vector<pdi> G[maxn];
int n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cout << fixed << setprecision(2);
int t, cas = 0; cin >> t;
while(t--){
cin >> n >> m;
for(int i = 0; i < n; ++i){
G[i].clear();
for(int j = 0; j < n; ++j) A[i][j] = 0;
}
while(m--){
int u, v; double r; cin >> u >> v >> r; --u, --v;
G[u].pb({r, v}), G[v].pb({r, u});
}
double Us = 1e10;
for(int i = 1; i < n - 1; ++i){
for(auto &e : G[i]){
int v = e.second; double r = e.first;
A[i - 1][i - 1] += 1 / r;
if(v == 0) A[i - 1][n - 2] += Us / r;
else if(v != n - 1) A[i - 1][v - 1] -= 1 / r;
}
}
gauss.solve(A, n - 2, n - 2);
double Is = 0, Req;
for(auto &e : G[n - 1]){
int v = e.second; double r = e.first;
if(v == 0) Is += Us / r;
else Is += A[v - 1][n - 2] / r;
}
Req = Us / Is;
cout << "Case #" << ++cas << ": " << Req << "\n";
}
return 0;
}
6、HDU - 4818 (https://vjudge.net/problem/HDU-4818)
题意:
有 n n n 个人,即 n n n 个结点,朋友关系为单向边,共 m m m 条边,假设第 i i i 个人有 x i x_i xi 点 R P RP RP,在每个时刻,每个人都会将他的 R P RP RP 均分单向传递给他的朋友,已知 ∑ i = 0 n − 1 x i = 1 \sum\limits_{i = 0}^{n - 1} x_i = 1 i=0∑n−1xi=1,问有多少种 R P RP RP 分配方案使得每个时刻传递 R P RP RP 后,每个人的 R P RP RP 仍保持不变。此外,若从结点 n − 1 n - 1 n−1 向外增加一条边,问连向谁能够使得 x n − 1 ′ > x n − 1 x_{n - 1}' \gt x_{n - 1} xn−1′>xn−1,且 x n − 1 ′ x_{n - 1}' xn−1′ 最大,优先连结点编号小的。 ( n ≤ 100 ) (n \leq 100) (n≤100)
解题思路:
上一道题的加强版,先接着上道题讲讲线代角度的理解。将图上的
n
n
n 个结点视为
n
n
n 维线性空间的一组基,图上的每条边对应为一个
n
n
n 维向量,那么针对图上的一个环而言,环上的向量必然线性有关(这很显然),并且,无环意味着边对应的向量组线性无关。那么图的边集对应的极大线性无关向量组的向量个数与图的生成树(或生成森林)是一样的,这可以通过判断图的连通分量数来确定。
每个结点处列出邻接边对应向量的线性组合,将涵盖所有边向量,其中极大线性无关向量组向量的个数,仍然不变。(每一条边总可以定向到某个结点)
回到题目,无穷解情况可以用连通性来辅助判断,当然也大可不必这样。
在前
n
−
1
n - 1
n−1 个结点处可以列出
n
−
1
n - 1
n−1 条方程,再加上
∑
i
=
0
n
−
1
x
i
=
1
\sum\limits_{i = 0}^{n - 1}x_i = 1
i=0∑n−1xi=1 作为最后一条, 共
n
n
n 条,依然是
A
x
=
b
Ax = b
Ax=b 的方程形式,高斯消元判解情况,其实也可以发现并不存在无解情况,
b
=
(
0
,
0
,
⋯
,
1
)
T
b = (0, 0, \cdots,1)^T
b=(0,0,⋯,1)T,该浮点数方程除了无穷解,就只有唯一解。
第二问着实推了一会,显然可以通过额外的
n
n
n 次消元来解决,但复杂度高达
O
(
n
4
)
O(n^4)
O(n4),连向单个结点的答案要在
O
(
n
2
)
O(n^2)
O(n2) 得到。首先考虑
u
(
=
n
−
1
)
→
v
u(=n - 1) \rightarrow v
u(=n−1)→v 边的增加会导致
A
A
A 如何变化,记
u
u
u 出度为
d
d
d,所有出点
v
i
v_i
vi 对应得到的
R
P
RP
RP 系数
−
1
d
(
d
+
1
)
- \cfrac{1}{d(d+1)}
−d(d+1)1,而
v
v
v 得到的
R
P
RP
RP 系数
+
1
d
+
1
+ \cfrac{1}{d + 1}
+d+11,在
A
A
A 矩阵中对应只改变第
n
−
1
n - 1
n−1 列,记该列向量为
c
c
c,记
A
′
=
A
+
C
A' = A + C
A′=A+C,其中
C
=
[
0
0
⋯
c
]
C = [~0~0~\cdots~c~]
C=[ 0 0 ⋯ c ]。
{
A
x
=
b
⇒
x
=
A
−
1
b
A
′
x
′
=
(
A
+
C
)
x
′
=
b
⇒
A
−
1
(
A
+
C
)
x
′
=
(
E
+
A
−
1
C
)
x
′
=
A
−
1
b
\begin{cases} Ax = b ~\Rightarrow ~ x = A^{-1}b\\ A'x' = (A + C)x' = b ~ \Rightarrow ~ A^{-1}(A + C)x' = (E + A^{-1}C)x' = A^{-1}b \end{cases}
{Ax=b ⇒ x=A−1bA′x′=(A+C)x′=b ⇒ A−1(A+C)x′=(E+A−1C)x′=A−1b
O
(
n
3
)
O(n^3)
O(n3) 处理出
A
−
1
A^{-1}
A−1,
A
−
1
b
A^{-1}b
A−1b 可以在
O
(
n
2
)
O(n^2)
O(n2) 得到,只需要求
x
n
−
1
′
x'_{n - 1}
xn−1′ 的话,只需要得到
E
+
A
−
1
C
E + A^{-1}C
E+A−1C 的最后一行,而实际上该行也只有最后一列一个非零元素,恰可以在
O
(
n
2
)
O(n^2)
O(n2) 得到。那么
n
n
n 次判断,总复杂度
O
(
n
3
)
O(n^3)
O(n3)。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 5e2 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
struct Gauss{
#define eps 1e-7
int piv[maxn], fre[maxn], tot;
int solve(double A[][maxn], int n, int m){
int r = 0; tot = 0;
for(int c = 0; c < m && r < n; ++c, ++r){
int mx = r;
for(int i = r + 1; i < n; ++i){
if(fabs(A[i][c]) > fabs(A[mx][c])) mx = i;
}
if(fabs(A[mx][c]) < eps) { --r, fre[tot++] = c; continue; }
piv[r] = c;
for(int j = c; j <= m; ++j) swap(A[r][j], A[mx][j]);
for(int i = 0; i < n; ++i){
if(i == r) continue;
double tmp = A[i][c] / A[r][c];
for(int j = c; j <= m; ++j) A[i][j] -= tmp * A[r][j];
}
}
for(int i = r; i < n; ++i) if(fabs(A[i][m]) > eps) return -1;
for(int i = 0; i < r; ++i){
double tmp = A[i][piv[i]];
for(int j = piv[i]; j <= m; ++j) A[i][j] /= tmp;
}
return r;
}
} gauss;
double A[maxn][maxn];
vector<int> G[maxn];
int out[maxn], n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int t; cin >> t;
while(t--){
cin >> n >> m;
for(int i = 0; i < n; ++i){
G[i].clear();
out[i] = 0;
for(int j = 0; j <= n * 2; ++j) A[i][j] = 0;
}
while(m--){
int u, v; cin >> u >> v;
G[u].pb(v), ++out[u];
}
for(int i = 0; i < n; ++i){
if(i != n - 1) A[i][i] = -1;
for(auto &v : G[i]){
A[v][i] += 1.0 / out[i];
}
}
for(int i = 0; i < n; ++i) A[n - 1][i] = 1;
for(int i = 0; i < n; ++i) A[i][i + n] = 1;
int rk = gauss.solve(A, n, n * 2);
if(rk < n || gauss.piv[n - 1] >= n){
cout << "INF\n";
continue;
}
int p = -1; double mn = 1e10;
for(int i = 0; i < n - 1; ++i){
double tmp = A[n - 1][i + n] / (out[n - 1] + 1);
for(auto &v : G[n - 1]){
if(v == i) { tmp = 0; break; }
tmp -= A[n - 1][v + n] / (out[n - 1] * (out[n - 1] + 1));
}
if(tmp < -eps && tmp + eps < mn) p = i, mn = tmp;
}
cout << "1 " << p << "\n";
}
return 0;
}
U
p
d
a
t
e
:
Update:
Update: 由于
A
A
A 只有第
n
−
1
n - 1
n−1 列发生变化,那么将所有可能的方案产生的列向量都添加到
A
A
A 后面,一起消元,就可以
O
(
n
3
)
O(n^3)
O(n3) 得到答案。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 5e2 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
struct Gauss{
#define eps 1e-7
int piv[maxn], fre[maxn], tot;
int solve(double A[][maxn], int n, int m){
int r = 0; tot = 0;
for(int c = 0; c < m && r < n; ++c, ++r){
int mx = r;
for(int i = r + 1; i < n; ++i){
if(fabs(A[i][c]) > fabs(A[mx][c])) mx = i;
}
if(fabs(A[mx][c]) < eps) { --r, fre[tot++] = c; continue; }
piv[r] = c;
for(int j = c; j <= m; ++j) swap(A[r][j], A[mx][j]);
for(int i = 0; i < n; ++i){
if(i == r) continue;
double tmp = A[i][c] / A[r][c];
for(int j = c; j <= m; ++j) A[i][j] -= tmp * A[r][j];
}
}
for(int i = r; i < n; ++i) if(fabs(A[i][m]) > eps) return -1;
for(int i = 0; i < r; ++i){
double tmp = A[i][piv[i]];
for(int j = piv[i]; j <= m; ++j) A[i][j] /= tmp;
}
return r;
}
} gauss;
double A[maxn][maxn];
vector<int> G[maxn];
int out[maxn], pre[maxn], n, m;
int fin(int x){
return x == pre[x] ? x : pre[x] = fin(pre[x]);
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int t; cin >> t;
while(t--){
cin >> n >> m;
for(int i = 0; i < n; ++i){
G[i].clear();
out[i] = 0;
for(int j = 0; j <= n * 2; ++j) A[i][j] = 0;
pre[i] = i;
}
while(m--){
int u, v; cin >> u >> v;
G[u].pb(v), ++out[u];
pre[fin(u)] = fin(v);
}
int tot = 0;
for(int i = 0; i < n; ++i) tot += i == fin(i);
if(tot != 1){
cout << "INF\n";
continue;
}
for(int i = 0; i < n; ++i){
if(i != n - 1) A[i][i] = -1;
for(auto &v : G[i]){
A[v][i] += 1.0 / out[i];
}
}
for(int i = 0; i < n; ++i) A[n - 1][i] = 1;
for(int i = 0; i < n - 1; ++i){
int flg = 0;
A[i][i + n] += 1.0 / (out[n - 1] + 1);
for(auto &v : G[n - 1]){
if(v == i) {flg = 1; break;}
A[v][i + n] -= 1.0 / (out[n - 1] * (out[n - 1] + 1));
}
if(flg) for(int j = 0; j < n; ++j) A[j][i + n] = 0;
else for(int j = 0; j < n; ++j) A[j][i + n] += A[j][n - 1];
}
A[n - 1][2 * n - 1] = 1;
int rk = gauss.solve(A, n, n * 2 - 1);
if(rk == n - 1 || gauss.piv[n - 1] >= n){
cout << "INF\n";
continue;
}
int p = -1; double mn = 1e10;
for(int i = 0; i < n - 1; ++i){
double tmp = A[n - 1][i + n];
if(tmp < eps) continue;
if(tmp + eps < 1 && tmp + eps < mn) p = i, mn = tmp;
}
cout << "1 " << p << "\n";
}
return 0;
}