K-Dimensional Foil HihoCoder - 1628 线性代数 解方程

题目大意:

  给你n艘宇宙飞船的前三维坐标,给你这些飞船之间的距离,问在至少多少维的空间里才能使这些飞船之间的距离都等于所给的距离。如果在大于等于3维的坐标下不能满足就输出“Goodbye World!”。(距离是K维空间下的欧几里得距离)

思路:

  首先明确一下所求的是飞船所在空间的最小维数,但是这个维数在不知道坐标的情况下是求不出来的。但是如果先求坐标再判断维数,会发现符合题意的坐标有很多个,直接求解解出来的是一个解系,不好直接解。如果暴力判断枚举范围很大,会超时。

  但是根据现代的理解,很容易发现这个解是单调的,也就是说如果假设解是k,那么所有大于等于k的维数都满足题中的距离,然后继续根据现代的理解可以知道,所求的最小维数k一定是在所给的n艘宇宙飞船的坐标(看成向量)相互线性无关的情况下(其中任一个向量不能由其他任一个向量线性组合而成)得到的。那么我们就可以想到,把这些宇宙飞船的坐标正交化必然可以得到一组每个向量都只有一维非零的向量,并且这个操作不改变这组向量所在的空间维数(虽然会让这个解不再符合题目所给的距离,但是不影响,因为不改变最终所求的空间最小维数),然后单位化再经过选择性地交换矩阵的两行必然可以得到一个单位矩阵(其实单位化是没有必要的,这里进行单位化只是为了让读者思路更清晰,最终目标是想得到一个下三角矩阵)。然后必然可以通过重复将矩阵一行乘以k加到,另一行上这个操作得到一个下三角矩阵。也就是说,虽然解有很多个,但是经过一系列操作可以让这个解变成下三角矩阵或者单位矩阵(这个时候可以明显看出所在空间最小维数)。换言之,我们可以直接通过尝试求解“是否存在一个前三维满足题意的n行下三角矩阵使得,这些向量之间的距离满足题意”来判断测试点是否有解。通过求解这个下三角矩阵同时求出维数。

算法:

1、预处理两两宇宙飞船之间的距离(减去前三维的坐标的影响),这样前三维就可以看成0,然后被忽略掉了。这样所求的矩阵(除去前三维的),就彻底成为一个下三角矩阵了。

2、矩阵由宇宙飞船的坐标构成(预处理后忽略前三维),第一行是第一艘宇宙飞船的坐标,第二行是第二艘,如此类推。(当然在进入第三步前,这些坐标都被初始化成0)

3、如果第一艘飞船和第二艘飞船的距离(预处理后的)不为0,固定第一艘飞船的新一维坐标为0(下三角矩阵的上三角必然为0)。通过第一艘飞船和第二艘之间的距离(预处理以后的),列方程解出第二艘飞船新增一维(原来 新增维数 k为0,现在为1)的坐标(其实就是sqrt(d12 ’),顺便记录下新增的第k维是在计算那一艘飞船的时候被新增的add[k] ;如果为0,则不新增维数,或者看成第二艘飞船新增维数对应的坐标值为0,进入下一步。

4、逐一计算第i艘宇宙飞船在新增的维数(k)上的坐标。具体方法是根据第一艘 飞船和第i艘飞船之间的距离,和第add[k]艘飞船之间的距离之间的关系,列一个距离的方程组(所有的k都有一个方程),这个方程组的未知数就是正在计算的这个i飞船的坐标。

代码:

//#pragma GCC optimize(2)
#pragma comment(linker, "/STACK:10240000,10240000")
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<ctime>
#include<ctype.h>
#include<stdlib.h>
#include<bitset>
#include<algorithm>
#include<assert.h>
#include<numeric> //accumulate
#define endl "\n"
#define fi first
#define se second
#define forn(i,s,t) for(int i=(s);i<=(t);++i)
#define mem(a,b) memset(a,b,sizeof(a))
#define rush() int MYTESTNUM;cin>>MYTESTNUM;while(MYTESTNUM--)
#define debug(x) printf("%d\n",x)
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define mp make_pair
#define pb push_back
#define sc(x) scanf("%d",&x)
#define sc2(x,y) scanf("%d%d",&x,&y)
#define sc3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define pf(x) printf("%d\n",x)
#define pf2(x,y) printf("%d %d\n",x,y)
#define pf3(x,y,z) printf("%d %d %d"\n,x,y,z)
#define ll unsigned long long
using namespace std;
const double eps=1e-6;//一开始eps=1e-12 WA了
const int maxn=60;
const ll P=1e9+7;
ll mul(ll a, ll b){ll ans = 0;for(;b;a=a*2%P,b>>=1) if(b&1) ans=(ans+a)%P;return ans;}
ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;}
ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;}
inline int read()
{
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
int n;
double a[maxn][maxn];//坐标
double dis[maxn][maxn];//距离
int num[maxn];//第一次增加t维度是在计算第num[t]艘宇宙飞船时
int main()
{
    rush()
    {
        n=read();
        for(int i=0;i<n;++i)
            for(int j=0;j<3;++j)
                a[i][j]=read();//读入坐标

        bool flag=0;//假设有解
        for(int i=0;i<n;++i)
            for(int j=i+1;j<n;++j)
            {
                dis[i][j]=read();
                for(int k=0;k<3;++k) dis[i][j]-=(a[i][k]-a[j][k])*(a[i][k]-a[j][k]);
                if(dis[i][j]<-eps/*这个负号是个坑*/){ flag=1;/*cout<<"At the begining"<<endl;*/}//一个数的平方为负数,说明在本题中无解。
                dis[j][i]=dis[i][j];//后面如果能一直保证顺序不敲错也可以不写这行
            }

        if(!flag)//初步判断有解时
        {
            mem(a,0);
            mem(num,0);
            int k=0;//一开始新增的维数为0
            for(int i=1;i<n;++i)
            {
                double now=dis[i][0];//第i艘飞船和0艘之间的距离
                for(int j=0;j<k;++j)
                {
                    if(a[num[j]][j]>eps)//如果大于0就要新增一个维度
                    {/*这里一直在解方程,k元二次方程组,通过手算前几个推出规律*/
                          double sum=0;
                          for(int p=0;p<k+1;++p)
                              sum+=(a[num[j]][p]-a[i][p])*(a[num[j]][p]-a[i][p]);
                          double mid=(sum-dis[i][num[j]]);
                          sum=0;
                          for(int p=0;p<k+1;++p)
                              sum+=(a[0][p]-a[i][p])*(a[0][p]-a[i][p]);
                          double mi=sum-dis[i][0];
                          mid-=mi;
                          a[i][j]=mid/2/a[num[j]][j];//解出这一行的第j个未知数
                    }
                    if(now-a[i][j]*a[i][j] < -eps/*前面WA了一次现在顺手把这个负号也加上*/){flag=1;/*cout<<"105"<<endl;*/break;}//在解下一个未知数前,剩下要算的距离平方已经为负数则无解
                    else now-=(a[i][j])*(a[i][j]);//否则继续算下一个未知数
                }
                if(flag==1) break;
                                   // cout<<"now "<<now<<endl;
                if(now>eps)
                {/*这里是最后一个未知数,因为循环最后一步已经判断是否大于0了,所有直接开平方解出来就行*/
                    num[k]=i;
                    a[i][k]=sqrt(now);
                    k++;//维数加一
                }
                //cout<<"now"<<sqrt(now)<<endl;
                for(int j=0;j<i;++j)
                {/*算的时候,是根据前面所有增加过维数的飞船,加上第一艘飞船和第i艘飞船之间的距离 列出来的方程组算出解的
                    这个解不一定满足那些没有新增维度的飞船给出的距离,所以检查一下*/
                    double sum=0;
                    for(int p=0;p<k+1;++p)
                        sum+=(a[j][p]-a[i][p])*(a[j][p]-a[i][p]);
                    if(fabs(sum-dis[i][j])>eps){flag=1;/*cout<<"116"<<endl;*/break;}
                }
            }
            if(!flag) printf("%d\n",k+3);//最后的答案要加上前3维
        }
        if(flag) cout<<"Goodbye World!"<<endl;//无解的输出
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/LS-Joze/p/11406305.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值