高斯消元

模板(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\) 个点是否联通

  • 那么根据题目大意,我们可以构造异或方程组

\[\begin{cases}a_{1,1}x_1 ⊕a_{1,2}x_2⊕\dots ⊕a_{1,n}x_n=1\\a_{2,1}x_1⊕a_{2,2}x_2⊕\dots ⊕a_{2,n}x_n=1\\\qquad\qquad\qquad\quad\:\:\:\vdots\\a_{n,1}x_1⊕a_{n,2}x_n⊕\dots⊕a_{n,n}x_n=1\\\end{cases} \]

  • 之后,我们使用高斯消元可得出一个上三角矩阵,可能会存在自由元。如果不存在,直接统计答案即可。如果存在,我们从后面开始 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;
}
posted @ 2023-08-07 07:41  xishanmeigao  阅读(46)  评论(0)    收藏  举报