刚刚写完了51nod上面的四点共面的题目,顿时信心满满的去hdu上找题目做,然后就发现了这道罗汉题目。。
题意:
三维空间n个点,找出有多少个四面体满足
1:至少四条边相等,不相等的两条边不相邻
2:六条边都相等
读完题目以后有点绝望,四点共面在哪呢?我百度出来什么玩意儿? emm,然后就开始想办法解决啊。
首先把四点共面的模板打上去,然后加了一个求两点距离的dis函数,函数部分的代码如下:
typedef long long int ll;
struct Point{
ll x,y,z;
}p[maxn];
ll dis(Point a,Point b){
return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y) + (a.z - b.z)*(a.z - b.z);
}
ll dMul(Point a,Point b)//求点积
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
Point xMul(Point a,Point b){
Point c;
c.x = a.y*b.z-a.z*b.y;
c.y = a.z*b.x-a.x*b.z;
c.z = a.x*b.y-b.x*a.y;
return c;
}
Point subPoint(Point a,Point b)
{
Point c;
c.x = a.x - b.x;
c.y = a.y - b.y;
c.z = a.z - b.z;
return c;
}
Point fa(Point a,Point b,Point c){
return xMul(subPoint(a,b),subPoint(a,c));
}
bool ok(Point a,Point b,Point c,Point d){
return dMul(fa(a,b,c),subPoint(a,d))==0;
}
然后就是看题意了,看了半个小时没什么想法,就去网上看了看其他同学的思路。
我认为这个题目正确的思路应该是这样的,首先我们需要枚举两个点作为等腰三角形的一条边,然后用一个数组(代码中为st数组)去记录点的集合里能与前面枚举的两个点构成等腰三角形的点,st数组里面存储的是符合这个条件的点的下标值(后面会用到)。之后的代码我觉得比较的需要理解,这里我们已经得到了很多个可以与最外层枚举的两个点构成等腰三角形的点,并且存在了st数组里,而随着每一次向st数组里的存储(st[l++] = k),l 也就是st中存储了的点的个数了。
这个时候我们再写一个双层for循环,你可能会讲,这不就是O(n^4)的复杂度了吗? 不会TLE吗? 先别急,这个问题在后面我们会讲。这个双层for循环要做的就是:从st数组中存储的值(点的下标)中,枚举出所有可以与外层循环构成符合题意的四面体的两个点。需要注意的是,当①四个点共面(构不成四面体)或②这两个点到外层枚举的两点中的任意一点距离不相等的时候(不满足题意),可以直接continue一下。当前面这两个条件都不满足的时候,我们就应该把数据记录下来。
做到这里你可能已经松了一口气,但是还没完,你仔细想想当四个点组成的是正四面体(每条边都相等)时,一种情况是不是被统计了6次?,不是正四面体时,每种情况也被统计了2次。所以我们还需要把正四面体的个数统计出来,所以在最后还需要一个if语句来记录正四面体的个数。
假设统计出的符合条件的四面体总个数为ans,正四面体的个数为aans。
最后符合条件的四面体的个数应该是:(ans-aans)/2 + aans/6= (ans/2)-(aans/3)个
或者你也可以这样: ans /= 2,aans /= 6;这样的话符合条件的四面体就有ans-2*aans个。
全部的代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define maxn 500
using namespace std;
typedef long long int ll;
struct Point{
ll x,y,z;
}p[maxn];
ll dis(Point a,Point b){
return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y) + (a.z - b.z)*(a.z - b.z);
}
ll dMul(Point a,Point b)//求点积
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
Point xMul(Point a,Point b){
Point c;
c.x = a.y*b.z-a.z*b.y;
c.y = a.z*b.x-a.x*b.z;
c.z = a.x*b.y-b.x*a.y;
return c;
}
Point subPoint(Point a,Point b)
{
Point c;
c.x = a.x - b.x;
c.y = a.y - b.y;
c.z = a.z - b.z;
return c;
}
Point fa(Point a,Point b,Point c){
return xMul(subPoint(a,b),subPoint(a,c));
}
bool ok(Point a,Point b,Point c,Point d){
return dMul(fa(a,b,c),subPoint(a,d))==0;
}
ll st[maxn];
int main()
{
int T,n;
int Case = 1;
scanf("%d",&T);
while(T--){
memset(st,0,sizeof(st));
scanf("%d",&n);
for(int i = 0;i < n;i ++){
scanf("%I64d %I64d %I64d",&p[i].x,&p[i].y,&p[i].z);
// cin>>p[i].x>>p[i].y>>p[i].z;
}
ll ans = 0,aans = 0,l;
for(int i = 0;i < n;i ++){
for(int j = i+1;j < n;j ++){
l = 0;
for(int k = 0;k < n;k ++){
if(dis(p[k],p[i])==dis(p[k],p[j]))
st[l++] = k;
}
// cout<<"l = "<<l<<endl;
for(int i1 = 0;i1 < l;i1 ++)
for(int j1 = i1+1;j1 < l;j1 ++){
if(dis(p[st[i1]],p[i])!=dis(p[st[j1]],p[i]))
continue;
if(ok(p[st[i1]],p[i],p[st[j1]],p[j]))
continue;
ans ++;
ll pre = dis(p[st[i1]],p[i]);
if(dis(p[st[i1]],p[st[j1]]) == pre && dis(p[i],p[j]) == pre)
aans++; //正四面体
}
}
}
cout<<(ans/2)-(aans/3)<<endl;
ans /= 2;
aans /= 6;
cout<<"ans = "<<ans<<" , aans = "<<aans<<endl;
printf("Case #%d: %I64d\n",Case++,ans-2*aans);
}
return 0;
}
对了,关于O(n^4)复杂度的问题,我是这么理解的:因为里面的两层循环便利的是刚刚好在外层循环组成的线段的垂直平分线上面的点,而这样的点的数量是远远没达到n的,所以我认为这里的时间复杂度应该是没有达到O(n^4)的,有其他看法的同学可以在下面留言,一起讨论!
每一天的努力都是为未来添砖加瓦!