高斯消元
模板(P2455)
#include<bits/stdc++.h>
using namespace std;
const int N=110;
const double eps=1e-12;
int n,id[N],line;
double a[N][N];
bool zero(double x)
{
return fabs(x)<eps;
}
void gauss()
{
line=1;
for(int i=1; i<=n; i++)
{
int mx=line;
for(int j=line+1; j<=n; j++)
if(fabs(a[mx][i])<fabs(a[j][i]))
mx=j;
if(zero(a[mx][i]))
continue;
for(int j=1; j<=n+1; j++)
swap(a[line][j],a[mx][j]);
for(int j=1; j<=n; j++)
{
if(line==j)
continue;
double tmp=a[j][i]/a[line][i];
for(int k=1; k<=n+1; k++)
a[j][k]-=a[line][k]*tmp;
}
id[i]=line++;
}
}
void NIE(int x)
{
printf("%d",x);
exit(0);
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
for(int j=1; j<=n+1; j++)
scanf("%lf",&a[i][j]);
gauss();
for(int i=1; i<=n; i++)
if(!id[i])
id[i]=line++;
for(int i=1; i<=n; i++)
if(zero(a[id[i]][i]) && !zero(a[id[i]][n+1]))
NIE(-1);
for(int i=1; i<=n; i++)
if(zero(a[id[i]][i]) && zero(a[id[i]][n+1]))
NIE(0);
for(int i=1; i<=n; i++)
{
double ans=a[id[i]][n+1]/a[id[i]][i];
if(zero(ans))
ans=0.00;
printf("x%d=%.2lf\n",i,ans);
}
return 0;
}
一些特殊情况
-
若存在系数全为 \(0\),常数不为 \(0\) 的行,则方程组无解
-
若系数不全为 \(0\) 的行恰好有 \(n\) 个,则说明主元有 \(n\) 个,方程有唯一解
-
若系数不全为 \(0\) 的行有 \(k<n\) 个,则说明主元有 \(k\) 个,自由元有 \(n-k\) 个,方程组有无穷多个解
Part 2:一点点习题
[USACO09NOV] Lights G
(题目传送门)
题目大意
给出一张 \(n\) 个点 \(m\) 条边的无向图,每个点的初始状态都为 \(0\)。
你可以操作任意一个点,操作结束后该点以及所有与该点相邻的点的状态都会改变,由 \(0\) 变成 \(1\) 或由 \(1\) 变成 \(0\)。
你需要求出最少的操作次数,使得在所有操作完成之后所有 \(n\) 个点的状态都是 \(1\)。
解题思路
-
设 \(x_i\) 表示第 \(i\) 个点是否操作,\(0\) 表示无,\(1\) 表示有
-
设 \(a_{i,j}\) 表示第 \(i\) 个点和第 \(j\) 个点是否联通
-
那么根据题目大意,我们可以构造异或方程组
- 之后,我们使用高斯消元可得出一个上三角矩阵,可能会存在自由元。如果不存在,直接统计答案即可。如果存在,我们从后面开始 dfs,对于所有的自由元,赋值成 \(0\) 或 \(1\) 继续 dfs 即可
#include<bits/stdc++.h>
using namespace std;
const int N=45;
const double eps=1e-12;
int n,m,val[N];
int a[N][N],ans=N;
bool gauss()
{
bool flag=1;
for(int i=1; i<=n; i++)
{
int mx=i;
for(mx=i; mx<=n; mx++)
if(a[mx][i])
break;
if(mx>n)
{
flag=0;
continue;
}
for(int j=1; j<=n+1; j++)
swap(a[i][j],a[mx][j]);
for(int j=1; j<=n; j++)
{
if(j==i || !a[j][i])
continue;
for(int k=i; k<=n+1; k++)
a[j][k]^=a[i][k];
}
}
return flag;
}
void dfs(int x,int num)
{
if(num>=ans)
return;
if(x==0)
ans=num;
if(a[x][x])
{
val[x]=a[x][n+1];
for(int i=x+1; i<=n; i++)
val[x]^=(a[x][i]&val[i]);
if(val[x])
dfs(x-1,num+1);
else
dfs(x-1,num);
}
else
{
val[x]=0;
dfs(x-1,num);
val[x]=1;
dfs(x-1,num+1);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[x][y]=a[y][x]=1;
}
for(int i=1; i<=n; i++)
a[i][i]=a[i][n+1]=1;
if(gauss())
{
ans=0;
for(int i=1; i<=n; i++)
ans+=a[i][n+1];
printf("%d",ans);
}
else
{
dfs(n,0);
printf("%d",ans);
}
return 0;
}
[JSOI2012] 始祖鸟
(题目传送门)
(双倍经验)
题目大意
现在有 \(N\) 只始祖鸟,我们从 \(1\) 开始编号。对于第 \(i\) 只始祖鸟,有 \(M_i\) 个认识的朋友,它们的编号分别是 \(F_{i,1},F_{i,2},\dots,F_{i,M_i}\)。朋友的认识关系是单向的,也就是说如果第\(s\)只始祖鸟认识第 \(t\) 只始祖鸟,那么第 \(t\) 只始祖鸟不一定认识第 \(s\) 只始祖鸟。
聚会的地点分为两处,一处在上游,一处在下游。对于每一处聚会场所,都必须满足对于在这个聚会场所中的始祖鸟,有恰好有偶数个自己认识的朋友与之在同一个聚会场所中。当然,每一只始祖鸟都必须在两处聚会场所之一。
现在需要你给出一种安排方式。你只需要给出在上游的始祖鸟编号,如果有多组解,请输出任何一组解。
\(1 \le N \le 2000\)。
解题思路
-
设第 \(i\) 只鸟的朋友为 \(a_{i,1}\dots a_{i,k}\),\(x_i\) 表示第 \(i\) 只鸟的状态。\(1\) 表示去上游,\(0\) 表示去下游。因为涉及到奇偶,所以我们按朋友数量的奇偶性来分类
-
若 \(k\) 是偶数
-
若第 \(i\) 只鸟去上游,因为去上游的朋友数量是偶数个,所以有 \(x_{a_{i,1}}⊕x_{a_{i,2}}⊕\dots ⊕x_{a_{i,k}}=0\)
-
若第 \(i\) 只鸟去下游,那么显然去上游的朋友数量也是偶数个(偶数 \(-\) 偶数 \(=\) 偶数),所以仍然 \(x_{a_{i,1}}⊕x_{a_{i,2}}⊕\dots ⊕x_{a_{i,k}}=0\)
-
-
若 \(k\) 是奇数
-
若第 \(i\) 只鸟去上游,仍然是 \(x_{a_{i,1}}⊕x_{a_{i,2}}⊕\dots ⊕x_{a_{i,k}}=0\)
-
若第 \(i\) 只鸟去下游,此时方程变了,变成 \(x_{a_{i,1}}⊕x_{a_{i,2}}⊕\dots ⊕x_{a_{i,k}}=1\)
-
考虑将两个方程变成同一个,观察到如果方程同时异或上 \(x_i\) 的话,那么等号右边就都是 \(1\),所以将方程改成 \(x_{a_{i,1}}⊕x_{a_{i,2}}⊕\dots ⊕x_{a_{i,k}}⊕x_i=1\)
-
-
bitset优化高斯消元即可
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int n;
bitset <N> a[N];
void NIE()
{
printf("Impossible");
exit(0);
}
void gauss()
{
for(int i=1; i<=n; i++)
{
int mx=i;
for(mx=i; mx<=n; mx++)
if(a[mx][i])
break;
if(mx>n)
continue;
swap(a[i],a[mx]);
for(int j=1; j<=n; j++)
{
if(j==i || !a[j][i])
continue;
a[j]^=a[i];
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
int m,x;
scanf("%d",&m);
for(int j=1; j<=m; j++)
{
scanf("%d",&x);
a[i][x]=1;
}
if(m&1)
a[i][i]=1,a[i][n+1]=1;
}
gauss();
int cnt=0;
for(int i=1; i<=n; i++)
{
if(!a[i][i] && a[i][n+1])
NIE();
if(a[i][n+1]&1)
cnt++;
}
printf("%d\n",cnt);
for(int i=1; i<=n; i++)
{
if(a[i][n+1])
printf("%d ",i);
}
return 0;
}

浙公网安备 33010602011771号