高斯消元

模板(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 个,自由元nk 个,方程组有无穷多个解

Part 2:一点点习题

[USACO09NOV] Lights G

题目传送门

题目大意

给出一张 n 个点 m 条边的无向图,每个点的初始状态都为 0

你可以操作任意一个点,操作结束后该点以及所有与该点相邻的点的状态都会改变,由 0 变成 1 或由 1 变成 0

你需要求出最少的操作次数,使得在所有操作完成之后所有 n 个点的状态都是 1

解题思路

  • xi 表示第 i 个点是否操作,0 表示无,1 表示有

  • ai,j 表示第 i 个点和第 j 个点是否联通

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

{a1,1x1a1,2x2a1,nxn=1a2,1x1a2,2x2a2,nxn=1an,1x1an,2xnan,nxn=1

  • 之后,我们使用高斯消元可得出一个上三角矩阵,可能会存在自由元。如果不存在,直接统计答案即可。如果存在,我们从后面开始 dfs,对于所有的自由元,赋值成 01 继续 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 只始祖鸟,有 Mi 个认识的朋友,它们的编号分别是 Fi,1,Fi,2,,Fi,Mi。朋友的认识关系是单向的,也就是说如果第s只始祖鸟认识第 t 只始祖鸟,那么第 t 只始祖鸟不一定认识第 s 只始祖鸟。

聚会的地点分为两处,一处在上游,一处在下游。对于每一处聚会场所,都必须满足对于在这个聚会场所中的始祖鸟,有恰好有偶数个自己认识的朋友与之在同一个聚会场所中。当然,每一只始祖鸟都必须在两处聚会场所之一。

现在需要你给出一种安排方式。你只需要给出在上游的始祖鸟编号,如果有多组解,请输出任何一组解。

1N2000

解题思路

  • 设第 i 只鸟的朋友为 ai,1ai,kxi 表示第 i 只鸟的状态。1 表示去上游,0 表示去下游。因为涉及到奇偶,所以我们按朋友数量的奇偶性来分类

  • k 是偶数

    • 若第 i 只鸟去上游,因为去上游的朋友数量是偶数个,所以有 xai,1xai,2xai,k=0

    • 若第 i 只鸟去下游,那么显然去上游的朋友数量也是偶数个(偶数 偶数 = 偶数),所以仍然 xai,1xai,2xai,k=0

  • k 是奇数

    • 若第 i 只鸟去上游,仍然是 xai,1xai,2xai,k=0

    • 若第 i 只鸟去下游,此时方程变了,变成 xai,1xai,2xai,k=1

    • 考虑将两个方程变成同一个,观察到如果方程同时异或上 xi 的话,那么等号右边就都是 1,所以将方程改成 xai,1xai,2xai,kxi=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 @   xishanmeigao  阅读(17)  评论(0编辑  收藏  举报
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示