文章目录
1、高斯消元求解开关问题
POJ 1222 EXTENDED LIGHTS OUT
题意:
给定一个
5
5
5行
6
6
6列的
01
01
01矩阵,每一个小格子代表一个开关,
0
0
0表示开关关闭,
1
1
1表示开关闭合,当按下一个开关后,其也会影响该开关上下左右的开关的状态。现在给定一个这样的01矩阵,输出一种方案,使得所有开关都变成闭合状态。
思路: 5 5 5行 6 6 6列,相当于一共是 30 30 30个未知数,可以根据题意列出 30 30 30个方程,然后高斯消元,因为都是 01 01 01,所以是解异或方程组。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int N = 35;
int n = 30, m;
int a[N][N], id[N][N];
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
void guass()
{
int r, c;
for(c = 0, r = 0; c < n; c ++)
{
int t = r; //找主元
for(int i = r + 1; i < n; i ++)
if(a[i][c]) { t = i; break; }
if(!a[t][c]) continue;//为0跳过
for(int i = c; i <= n; i ++) swap(a[t][i], a[r][i]);//交换行
for(int i = r + 1; i < n; i ++)//消元
if(a[i][c])
for(int j = c; j <= n; j ++)
a[i][j] ^= a[r][j];
r ++;
}
for(int i = r - 1; i >= 0; i --)//倒推求解
for(int j = i + 1; j < n; j ++)
a[i][n] ^= a[i][j] & a[j][n];
}
int main()
{
#ifdef LOCAL
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
int t; cin >> t;
for(int i = 0, k = 0; i < 5; i ++)
for(int j = 0; j < 6; j ++)
id[i][j] = k ++;
int kase = 0;
while(t --)
{
memset(a, 0, sizeof a);
for(int i = 0; i < n; i ++)
{
cin >> a[i][n];
a[i][i] = 1;
}
for(int i = 0; i < 5; i ++)
for(int j = 0; j < 6; j ++)
for(int k = 0; k < 4; k ++)
{
int ni = i + dx[k], nj = j + dy[k];
if(ni < 0 || ni >= 5 || nj < 0 || nj >= 6) continue;
int u = id[i][j], v = id[ni][nj];
a[u][v] = 1;
}
guass();
printf("PUZZLE #%d\n", ++ kase);
for(int i = 0; i < 5; i ++)
{
for(int j = 0; j < 6; j ++)
printf("%d ", a[id[i][j]][n]);
putchar('\n');
}
}
return 0;
}
POJ 1753 Flip Game
题意:
相当于给定一个
01
01
01矩阵,求全部变成0或则全部变成1的最小步数。
注意:
当求解方程时,出现了自由元的时候,要去枚举自由元的值。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int N = 20;
int n, a[N][N], b[N][N], cp[N][N];
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
int get(int x, int y)
{
return 4 * x + y;
}
int guass(int a[][N])
{
int r, c;
for(r = 0, c = 0; c < n; c ++)
{
int t = r;
for(int i = r + 1; i < n; i ++)
if(a[i][c]) { t = i; break; }
if(!a[t][c]) continue;
for(int i = c; i <= n; i ++) swap(a[t][i], a[r][i]);
for(int i = r + 1; i < n; i ++)
if(a[i][c])
for(int j = c; j <= n; j ++)
a[i][j] ^= a[r][j];
r ++;
}
if(r < n)
{
for(int i = r; i < n; i ++)
if(a[i][n]) return INF;
}
//需要枚举变元的值
int res = INF, t = n - r; //t是变元的个数
for(int i = 0; i < 1 << t; i ++)
{
int tmp = 0;
memcpy(cp, a, sizeof cp);
for(int j = 0; j < t; j ++)
if(i >> j & 1) cp[n - j - 1][n] = 1, tmp ++;
for(int j = r - 1; j >= 0; j --)
{
for(int k = j + 1; k < n; k ++)
cp[j][n] ^= cp[j][k] & cp[k][n];
tmp += cp[j][n];
}
//cout << tmp << endl;
res = min(res, tmp);
}
return res;
}
int main()
{
#ifdef LOCAL
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
n = 16;
for(int i = 0; i < 4; i ++)
for(int j = 0; j < 4; j ++)
{
char ch; cin >> ch;
int id = get(i, j);
if(ch == 'b') a[id][n] = 1;
else b[id][n] = 1;
a[id][id] = b[id][id] = 1;
}
for(int i = 0; i < 4; i ++)
for(int j = 0; j < 4; j ++)
for(int k = 0; k < 4; k ++)
{
int ni = i + dx[k], nj = j + dy[k];
if(ni < 0 || ni >= 4 || nj < 0 || nj >= 4) continue;
int u = get(i, j), v = get(ni, nj);
a[u][v] = b[u][v] = 1;
}
int ans = min(guass(a), guass(b));
if(ans == INF) puts("Impossible");
else cout << ans << endl;
return 0;
}
P2962 [USACO09NOV]Lights G
本题写法与上面有些不同,因为自由元可能较多,上面的枚举方法肯定会TLE,所以采用dfs+剪枝枚举。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int N = 55;
int n, m, a[N][N], x[N];
int ans = INF;
void gauss()
{//这里的写法没有将所有无解或无穷解的行移到最下面,方便dfs枚举
//保证了第i行一定是第i个未知数的方程
for(int c = 0; c < n; c ++)
{
int t = c;
for(int i = c; i < n; i ++)
if(a[i][c]) { t = i; break; }
if(!a[t][c]) continue;
for(int i = c; i <= n; i ++) swap(a[t][i], a[c][i]);
for(int i = c + 1; i < n; i ++)
if(a[i][c])
for(int j = c; j <= n; j ++)
a[i][j] ^= a[c][j];
}
}
void dfs(int k, int tot)
{
if(tot >= ans) return ; //剪枝
if(k < 0) { ans = tot; return ; }
if(a[k][k]) //不是自由元,正常倒推求解
{
x[k] = a[k][n];
for(int i = k + 1; i < n; i ++)
x[k] ^= a[k][i] & x[i];
dfs(k - 1, tot + x[k]);
}
else//是自由元,枚举取值
{
x[k] = 0; dfs(k - 1, tot);
x[k] = 1; dfs(k - 1, tot + 1);
}
}
int main()
{
#ifdef LOCAL
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
cin >> n >> m;
while(m --)
{
int u, v;
cin >> u >> v;
u --, v --;
a[u][v] = a[v][u] = 1;
}
for(int i = 0; i < n; i ++) a[i][i] = a[i][n] = 1;
gauss();
dfs(n - 1, 0);
cout << ans << endl;
return 0;
}
2、高斯消元求解线性方程组
P3389 【模板】高斯消元法
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int N = 110;
const double eps = 1e-6;
int n;
double a[N][N];
int guass()
{
int r, c;
for(r = 0, c = 0; c < n; c ++)
{
int t = r;//找主元
for(int i = r + 1; i < n; i ++)
if(fabs(a[i][c]) > fabs(a[t][c])) t = i;
if(fabs(a[t][c]) < eps) continue;//为0跳过
for(int i = c; i <= n; i ++) swap(a[t][i], a[r][i]);//交换行
for(int i = n; i >= c; i --) a[r][i] /= a[r][c];//归1
for(int i = r + 1; i < n; i ++)//消元
if(fabs(a[i][c]) > eps)
for(int j = n; j >= c; j --)
a[i][j] -= a[i][c] * a[r][j];
r ++;
}
if(r < n)
{
for(int i = r; i < n; i ++)
if(fabs(a[i][n]) > eps) return -1;
return 0;
}
for(int i = n - 1; i >= 0; i --)//倒推求解
for(int j = i + 1; j < n; j ++)
a[i][n] -= a[i][j] * a[j][n];
}
int main()
{
#ifdef LOCAL
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
cin >> n;
for(int i = 0; i < n; i ++)
for(int j = 0; j <= n; j ++) cin >> a[i][j];
int t = guass();
if(t < 1) puts("No Solution");
//else if(t == 0) puts("Infinite group solutions");
else
for(int i = 0; i < n; i ++) printf("%.2lf\n", a[i][n]);
return 0;
}
P2973 [USACO10HOL]Driving Out the Piggies G
做法:
设
f
i
f_i
fi表示到达i点的概率,则在i点爆炸的概率就是
p
q
∗
f
i
\frac{p}{q}*f_i
qp∗fi
f i f_i fi的递推公式可以表示为 f i = ∑ j → i f j ∗ ( 1 − p q ) ∗ 1 d e g r e e j f_i=\sum_{j\to i}f_j*(1-\frac{p}{q})*\frac{1}{degree_j} fi=∑j→ifj∗(1−qp)∗degreej1,其中 d e g r e e j degree_j degreej是 j j j的的度数。
但是本题无法直接 d p dp dp,本图是无向图,所以这个递推方程是有环的,有后效性。
我们可以将每一个 f i f_i fi看成一个未知数,这样就有 n n n个未知数,根据递推公式对于每一个 i i i都可以列出一个方程,相当于有了 n n n个方程,所以我们可以直接高斯消元求解。
要注意一点的是:本题的起点是 1 1 1号点,在列方程的时候要考虑 1 1 1号点的特殊性。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int N = 310;
const double eps = 1e-9;
int n, m;
double p, q, a[N][N];
int degree[N], g[N][N];
void guass()
{
int r, c;
for(r = 0, c = 0; c < n; c ++)
{
int t = r;
for(int i = r + 1; i < n; i ++)
if(fabs(a[i][c]) > fabs(a[t][c])) t = i;
if(fabs(a[t][c]) < eps) continue;
for(int i = c; i <= n; i ++) swap(a[t][i], a[r][i]);
for(int i = n; i >= c; i --) a[r][i] /= a[r][c];
for(int i = r + 1; i < n; i ++)
if(fabs(a[i][c]) > eps)
for(int j = n; j >= c; j --)
a[i][j] -= a[i][c] * a[r][j];
r ++;
}
for(int i = n - 1; i >= 0; i --)
for(int j = i + 1; j < n; j ++)
a[i][n] -= a[i][j] * a[j][n];
}
int main()
{
#ifdef LOCAL
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
cin >> n >> m >> p >> q;
p = 1.0 * p / q;
while(m --)
{
int u, v;
cin >> u >> v;
u --, v --;
g[u][v] = g[v][u] = 1;
degree[u] ++, degree[v] ++;
}
for(int i = 0; i < n; i ++)
{
a[i][i] = 1.0;
for(int j = 0; j < n; j ++)
if(g[j][i]) a[i][j] = -(1.0 - p) / degree[j];
}
a[0][n] = 1.0;//因为原本就在1号点,初值设为1
guass();
for(int i = 0; i < n; i ++)
printf("%.9lf\n", a[i][n] * p);
return 0;
}