高斯消元

北文 讲的。

\(O(n^3)\) 的复杂度求解线性方程组。

\[a_{11}x_1+a_{12}x_2+...+a_{1n}x_n=b_1 \]

\[... \]

\[a_{n1}x_1+a_{n2}x_2+...+a_{nn}x_n=b_n \]

约旦消元法:

  • 选择一个未选的未知数为主元,选择一个包含该主元的方程。

  • 将该方程的主元系数化为 \(1\) .

  • 通过加减消元消掉其他方程中的这个未知数。

  • 重复上述步骤。

操作完之后的方程组长这样:

\[c_1x_1=d_1 \]

\[... \]

\[c_nx_n=d_n \]

将等号右边的值除以系数即为所求。

存在 \(c_i=0,d_i\not=0\) 时无解。

此外存在 \(c_i=0,d_i=0\) 时有无数组解。


P3389 【模板】高斯消元法

不存在唯一解输出 No Solution .

#define N 105
#define db double
using namespace std;
int n;
db a[N][N];
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]);
	for(int i=1;i<=n;i++){
		int mx=i;
		for(int j=i+1;j<=n;j++)
			if(fabs(a[j][i])>fabs(a[mx][i]))mx=j;
		for(int j=1;j<=n+1;j++)
			swap(a[i][j],a[mx][j]);
		if(!a[i][i]){
			printf("No Solution\n");
			return 0;
		}
		for(int j=1;j<=n;j++){
			if(j==i)continue;
			db tp=a[j][i]/a[i][i];
			for(int k=i+1;k<=n+1;k++)
				a[j][k]-=a[i][k]*tp;
		}
	}
	for(int i=1;i<=n;i++)
		printf("%.2lf\n",a[i][n+1]/a[i][i]);
	
	return 0;
}

P2455 [SDOI2006]线性方程组

同样是板子题。

有点不一样写了半天。

#include<bits/stdc++.h>
#define db double
#define N 55
using namespace std;
int n,cur;
db a[N][N];
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		for(int j=0;j<n+1;j++)
			scanf("%lf",&a[i][j]);
	for(int i=0;i<n;i++){
		int mx=cur;
		for(int j=cur+1;j<n;j++)
			if(fabs(a[j][i])>fabs(a[mx][i]))mx=j;
		if(!a[mx][i])continue;
		for(int j=0;j<n+1;j++)
			swap(a[cur][j],a[mx][j]);
		for(int j=0;j<n;j++){
			if(j==cur)continue;
			db tp=a[j][i]/a[cur][i];
			for(int k=i+1;k<n+1;k++)
				a[j][k]-=a[cur][k]*tp;
		}
		cur++;
	}
	if(cur<n){
		for(int i=cur;i<n;i++)
			if(a[i][n]){
				printf("-1\n");
				return 0;
			}
		printf("0\n");
		return 0;
	}
	for(int i=0;i<n;i++)
		printf("x%d=%.2lf\n",i+1,a[i][n]/a[i][i]);
	
	return 0;
}

P2962 [USACO09NOV]Lights G

一个无向图,节点初始为 \(0\) .

一次操作可以使一个点及其相邻点权值 \(\text{xor}\space 1\) .

问使 \(n\) 个点都变为 \(1\) 的最小步数。

也就是求解异或方程组。

异或与加乘法都满足交换、结合律,可以按照高消的过程写。

会消出来一些自由元,暴力回代剪枝 \(\text{dfs}\) .

每题高消写的都不太一样。

只能说有一定道理。

#include<bits/stdc++.h>
#define N 40
using namespace std;
int read(){
	int x=0,w=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*w;
}
int n,m;
int a[N][N],ans=N;
int f[N];
void gauss(){
	for(int i=1;i<=n;i++){
		int mx=i;
		for(int j=i+1;j<=n;j++)
			if(a[j][i]){mx=j;break;}
		for(int j=1;j<=n+1;j++)
			swap(a[mx][j],a[i][j]);
		for(int j=i+1;j<=n;j++)
			if(a[j][i]){
				for(int k=1;k<=n+1;k++)
					a[j][k]^=a[i][k];
			}
	}
}
void dfs(int x,int tot){
	if(tot>ans)return;
	if(!x){
		ans=min(ans,tot);
		return;
	}
	if(a[x][x]){
		f[x]=a[x][n+1];
		for(int i=n;i>x;i--)
			f[x]^=f[i]&a[x][i];
		dfs(x-1,tot+f[x]);
	}
	else{
		f[x]=0,dfs(x-1,tot);
		f[x]=1,dfs(x-1,tot+1);
	}
}
int main(){
	n=read(),m=read();
	for(int u,v;m;m--){
		u=read(),v=read();
		a[u][v]=a[v][u]=1;
	}
	for(int i=1;i<=n;i++)
		a[i][i]=a[i][n+1]=1;
	gauss(),dfs(n,0);
	printf("%d\n",ans);
	
	return 0;
}

P2447 [SDOI2010] 外星千足虫

顺次加入异或方程组,判断何时存在唯一解。

每次消元都要向下找一个最小 \(a[j][i]=1\) ,于是便可满足题目要求。

贺一下看看,用 bitset 优化。

#include<bits/stdc++.h>
#define N 1010
#define M 2010
#define bs bitset<N>
using namespace std;
int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
int gets(){
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	return ch-'0';
}
int n,m;
bs a[M];
int ans;
int main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n+1;j++)
			a[i][j]=gets();
	}
	for(int i=1;i<=n;i++){
		int cur=i;
		while(cur<=m&&!a[cur][i])cur++;
		if(cur==m+1){
			printf("Cannot Determine\n");
			return 0;
		}
		ans=max(ans,cur);
		swap(a[cur],a[i]);
		for(int j=1;j<=m;j++){
			if(j==i)continue;
			if(a[j][i])a[j]^=a[i];
		}
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)
		printf(a[i][n+1]?"?y7M#\n":"Earth\n");
	
	return 0;
}

P6126 [JSOI2012]始祖鸟

一个点会向若干其他点连单向边。

将它们分成两部分,使得每一个点的部分中都有偶数个与其连边的点。

还原编号或输出无解。

\(n\le 2000\) .

对点 \(i\) 在上下游设状态 \(0,1\) .

对于点 \(i\)

\[\bigoplus_{j\in s_i}^{n}(a_j\space\text{xor}\space a_i\space\text{xor}\space1)=0 \]

\[(\bigoplus_{j\in s_i}^{n}a_j)\space \text{xor}\space(|s_i\And 1|\space a_i)=s_i\And 1 \]

那么 \(a_i\) 这位就可能取 \(0,1\) ,利用 bitset 求解异或方程组即可。

#include<bits/stdc++.h>
#define N 2010
using namespace std;
int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
int n,m;
bitset<N>a[N];
int ans,cnt;
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		m=read();
		a[i][i]=a[i][n+1]=m&1;
		while(m--)
			a[i][read()]=1;
	}
	for(int i=1;i<=n;i++){
		int mx=i;
		for(int j=mx;j<=n;j++)
			if(a[j][i]){mx=j;break;}
		swap(a[mx],a[i]);
		if(!a[i][i])continue;
		cnt++;
		for(int j=1;j<=n;j++)
			if(j!=i&&a[j][i])a[j]^=a[i];
	}
	if(cnt<n){
		for(int i=cnt+1;i<=n;i++)
			if(!a[i][i]&&a[i][n+1]){
				printf("Impossible\n");
				return 0;
			}
	}
	for(int i=1;i<=n;i++)
		ans+=a[i][n+1];
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)
		if(a[i][n+1])printf("%d ",i);
	printf("\n");
	
	return 0;
}

P4457 [BJOI2018]治疗之雨

高斯消元优化dp放这么难?

英雄当前血量 \(p\) ,上限血量 \(n\) ,有 \(m\) 个血量为 \(\text{inf}\) 的随从,不存在上限血量。

每次给随机一位非满血的友方角色治疗 \(1\) 点,接着执行 \(k\) 次操作,使随机一位友方角色扣 \(1\) 点血。

问期望几次治疗后英雄寄掉。

\(T\le 100\)\(p,n\le 1500\)\(0\le m,k\le 10^9\) .

\(P(i)\) 为扣 \(i\) 滴血的概率,有

\[P(i)=\frac{\binom{k}{i}m^{k-i}}{(m+1)^k} \]

\(f(i)\) 为血量为 \(i\) 的期望次数。

转移是

\[f(x)=\frac{1}{m+1}\sum_{i=0}^{x+1}f(i)P(x+1-i)+\frac{m}{m+1}\sum_{i=0}^{x}f(i)P(x-i)+1 \]

\[f(n)=\sum_{i=0}^{n}P(i)f(n-i)+1 \]

注意 \(\forall i>k,P(i)\rightarrow 0\) .

那么只需要处理 \(n+1\)\(P\) ,递推式

\[P(i)=P(i-1)\cdot\frac{k-i+1}{mi} \]

化一下

\[\sum_{i=0}^{x}\frac{P(x+1-i)+mP(x-i)}{m+1}f(i)+\frac{P(0)}{m+1}f(x+1)-f(x)=-1 \]

\[\sum_{i=0}^{n}f(i)P(n-i)-f(n)=-1 \]

边界 \(f(0)=0\) .

长得就像高斯消元了,但是 \(O(n^3)\) 不可过。

做的时候发现每次只消剩两个数,也就是只需要处理这两列。

时间复杂度 \(O(n^2)\) .

这题特判比较多。

#include<bits/stdc++.h>
#define N 1505
#define Mod 1000000007
using namespace std;
int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
int qpow(int k,int b,int p){
	int ret=1;k%=p;
	while(b){
		if(b&1)ret=1ll*ret*k%p;
		k=1ll*k*k%p,b>>=1;
	}
	return ret;
}
int inv(int x){
	return qpow(x,Mod-2,Mod);
}
int T,n,p,m,k;
int im,im1,P[N];
int a[N][N],iv[N];
void init(){
	for(int i=1;i<N;i++)
		iv[i]=inv(i);
}
int main(){
	T=read(),init();
	while(T--){
		n=read(),p=read(),m=read(),k=read();
		if(!k){
			printf("-1\n");
			continue;
		}
		if(!m){
			if(k==1)printf("-1\n");
			else{
				if(p!=n)printf("%d\n",(p-1)/(k-1)+1);
				else{
					p-=k,p=p>0?(p-1)/(k-1)+1:0; 
					printf("%d\n",p+1);
				}
			}
			continue;
		}
		memset(P,0,sizeof(P));
		memset(a,0,sizeof(a));
		im=inv(m),im1=inv(m+1),P[0]=qpow(1ll*m*im1%Mod,k,Mod);
		for(int i=1;i<=min(n,k);i++)
			P[i]=1ll*P[i-1]*(k-i+1)%Mod*iv[i]%Mod*im%Mod;
		for(int i=1;i<n;i++){
			for(int j=1;j<=i;j++)
				a[i][j]=(1ll*P[i+1-j]+1ll*m*P[i-j])%Mod*im1%Mod;
			a[i][i+1]=1ll*P[0]*im1%Mod,(a[i][i]+=Mod-1)%=Mod,a[i][n+1]=Mod-1;
		}
		for(int i=1;i<=n;i++)
			a[n][i]=P[n-i];
		(a[n][n]+=(Mod-1))%=Mod,a[n][n+1]=Mod-1;
		for(int i=1,tiv;i<=n;i++){
			if(!a[i][i])continue;
			tiv=inv(a[i][i]);
			for(int j=1;j<=n;j++){
				if(i==j||!a[j][i])continue;
				int tp=1ll*a[j][i]*tiv%Mod;
				if(i<n)a[j][i+1]=(1ll*a[j][i+1]-1ll*a[i][i+1]*tp%Mod+Mod)%Mod;
				a[j][n+1]=(1ll*a[j][n+1]-1ll*a[i][n+1]*tp%Mod+Mod)%Mod;
			}
		}
		printf("%d\n",1ll*a[p][n+1]*inv(a[p][p])%Mod);
	}
	
	return 0;
}
posted @ 2023-08-06 19:54  SError  阅读(11)  评论(0编辑  收藏  举报