【题解】P4035 [JSOI2008]球形空间产生器(线代,矩阵,高斯消元)

【题解】P4035 [JSOI2008]球形空间产生器


题目链接:P4035 [JSOI2008]球形空间产生器

思路分析:

  • 首先根据题意可以直接列出来这个式子:\(\sum \limits_{j=1}^{n}(a_{i,j}-x_j)^2=C\)。其中 \(i \in [1,n+1]\),球面上的第 \(i\) 个点是 \((a_{i,1},a_{i,2},…,a_{i,n})\),球心的在 \(n\) 维平面上的坐标是\((x_1,x_2,x_3,…,x_n)\)。所以该方程组实际上是 \(n+1\)\(n\) 元二次方程组,\(C\) 是未知常数。

  • 考虑我们现在要达成的目标:

    1. 由于 \(C\) 是未知的,所以我们要消掉 \(C\)

    2. 由于高斯消元本身并非二次方程组,所以我们要想办法降次,把它变为一次

    所以我们下来要做的,都是要围绕这两个目标进行。

    先考虑消元

    想到我们在学方程的时候的消元办法:可以把两个一样的方程做差。

    在这道题中,由于我们的方程组的所有方程的右边都是 \(C\),我们就可以考虑让相邻两个方程做差。就可以转化成 \(n\)\(n\) 元一次方程组:

    \[ \sum \limits_{j=1}^{n}[(a_{i,j}-x_j)^2-(a_{{i+1},j}-x_j)^2]=0(i=1,2,3,…,n) \]

    两个易错点

    1. 由于 \(j\) 表示的是维度,而不是点编号,所以 \(j\) 的下标是从 1 开始而不是 0 开始,这一点甚至在李煜东的《算法竞赛进阶指南》中都错了,千万不要把 \(i\)\(j\) 搞反。我刚开始也只是单纯接受书上说的没经过思考,是 dbxxx 大佬发现的(%他)

    2. 相邻两个方程对应的只是 \(i\) 的变化,\(j\) 并没有改变。

    再考虑降次

    看到这么复杂的式子,我们应该先把它展开:

    \[ \sum \limits_{j=1}^{n}({a_{i,j}}^2-{a_{i+1,j}}^2-2x_j(a_{i,j}- a_{i+1,j}))=0(i=1,2,3,…,n) \]

    观察式子可以发现,式子中的 \(a_{i,j}^2-a_{i+1,j}^2\) 实际上是常数,也就是说,上述式子中跟未知数 \(x_j\) 有关的项根本不含二次项

    所以我们直接通过移项,将变量放在等号左边,常数项放在等号右边,以达到降次的目的:

    \[ \sum \limits_{j=1}^{n}2(a_{i,j}-a_{i+1,j})x_j=\sum\limits_{j=1}^{n}({a_{i,j}}^2-{a_{{i+1},j}}^2)(i=1,2,3,…,n) \]

    于是,这就变成了一个线性方程组,直接用高斯消元求解即可!

    为了方便读者理解,我把构造出来的高斯消元矩阵放出来:

    \[\begin{bmatrix}2(a_{1,1}-a_{2,1})&2(a_{1,2}-a_{2,2})&\cdots&2(a_{1,n}-a_{2,n})&\sum\limits_{j=1}^{n}({a_{1,j}}^2-{a_{2,j}}^2)\\2(a_{2,1}-a_{3,1})&2(a_{2,2}-a_{3,2})&\cdots&2(a_{2,n}-a_{3,n})&\sum\limits_{j=1}^{n}({a_{2,j}}^2-{a_{3,j}}^2)\\\vdots&\vdots&\ddots&\vdots&\vdots\\2(a_{n,1}-a_{n+1,1})&2(a_{n,2}-a_{n+1,2})&\cdots&2(a_{n,n}-a_{n+1,n})&\sum\limits_{j=1}^{n}({a_{n,j}}^2-{a_{n+1,j}}^2)\end{bmatrix} \]

代码实现:

//luoguP4035
#include<iostream>
#include<cstdio>
#include<iomanip>
using namespace std;
const int maxn=15;
double a[maxn][maxn],c[maxn][maxn],b[maxn];
int n,m;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void gauss()
{
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
		{
			if(c[j][i])
			{
				for(int k=1;k<=m;k++)swap(c[j][k],c[i][k]);
				double tt=c[i][i];
		                for(int k=1;k<=m;k++)c[i][k]/=tt;
				break;
			}
		}
		for(int j=1;j<=n;j++)
		{
			if(j==i)continue; 
			double tt=c[j][i];
			for(int k=1;k<=m;k++)c[j][k]-=tt*c[i][k];
		}
	}
	for(int i=1;i<=n;i++)cout<<fixed<<setprecision(3)<<c[i][m]<<" ";
}

int main()
{
	n=read();m=n+1;
	for(int i=1;i<=n+1;i++)
	{
		for(int j=1;j<=n;j++)cin>>a[i][j];
	}
	//C:系数矩阵;B:常数——构造高斯消元矩阵 
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			c[i][j]=2*(a[i][j]-a[i+1][j]);
			b[i]+=(a[i][j]*a[i][j]-a[i+1][j]*a[i+1][j]); 
		}
		c[i][n+1]=b[i];
	}
	gauss();
	return 0;
}

一点说明:实际上,我本来不打算把我写的这些跟题目本身有关的总结放到博客里的(而是丢在洛谷剪切板里),主要是因为,我写的那些总结是给自己看的不是给别人看的,应该说我写的总结甚至不能算是一篇题解。题解的终极目标是要教会别人,而不是仅仅让自己懂。现在网上有太多低质量题解,题解作者为了自己明白一道题,写一篇类似总结的东西,然而他写的内容很多都是一些高度浓缩非人类语言,甚至有些题解只有代码。。。然后放在网上,美其名曰“题解”,最后的结果是自己懂了,但是读题解的人更加迷惑。我自己就深受其害。我并不确定我写的那些东西是否能够做到让别人明白,所以并不打算放到网上。

但是由于在写一些关于高斯消元的总结,需要用到一些例题的讲解过程,所以我在反复斟酌之后,发了这一篇看起来“应该算的上题解”的题解。如果有什么不理解的地方,请在评论区留言(虽然本来也没有几个人会看),我会改正完善自裁的。

posted @ 2022-04-11 22:33  向日葵Reta  阅读(43)  评论(0编辑  收藏  举报