【题解】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\) 是未知常数。
-
考虑我们现在要达成的目标:
-
由于 \(C\) 是未知的,所以我们要消掉 \(C\)。
-
由于高斯消元本身并非二次方程组,所以我们要想办法降次,把它变为一次。
所以我们下来要做的,都是要围绕这两个目标进行。
先考虑消元。
想到我们在学方程的时候的消元办法:可以把两个一样的方程做差。
在这道题中,由于我们的方程组的所有方程的右边都是 \(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) \]两个易错点:
-
由于 \(j\) 表示的是维度,而不是点编号,所以 \(j\) 的下标是从 1 开始而不是 0 开始,这一点甚至在李煜东的《算法竞赛进阶指南》中都错了,千万不要把 \(i\) 和 \(j\) 搞反。
我刚开始也只是单纯接受书上说的没经过思考,是 dbxxx 大佬发现的(%他) -
相邻两个方程对应的只是 \(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;
}
一点说明:实际上,我本来不打算把我写的这些跟题目本身有关的总结放到博客里的(而是丢在洛谷剪切板里),主要是因为,我写的那些总结是给自己看的不是给别人看的,应该说我写的总结甚至不能算是一篇题解。题解的终极目标是要教会别人,而不是仅仅让自己懂。现在网上有太多低质量题解,题解作者为了自己明白一道题,写一篇类似总结的东西,然而他写的内容很多都是一些高度浓缩非人类语言,甚至有些题解只有代码。。。然后放在网上,美其名曰“题解”,最后的结果是自己懂了,但是读题解的人更加迷惑。我自己就深受其害。我并不确定我写的那些东西是否能够做到让别人明白,所以并不打算放到网上。
但是由于在写一些关于高斯消元的总结,需要用到一些例题的讲解过程,所以我在反复斟酌之后,发了这一篇看起来“应该算的上题解”的题解。如果有什么不理解的地方,请在评论区留言(虽然本来也没有几个人会看),我会改正完善自裁的。