这道题考试的时候,认准暴力!可过45%!
正解:看了一篇大佬的解法,最核心的在于使用了一个map将边和点联系起来了。
很多小伙伴应该和本蒟蒻一样,不想直接暴力所有点,而是想把边弄一弄,然后兴致勃勃算了算所有情况的边,但是却发现,边的长度是有了,然而我怎么知道这个长度的边是属于ab,而不是属于bc呢?用一个Struct试试?存点和边。
好方法!然后我们就需要遍历这个集合,找相同的边,然后判断4个点之间的关系,恭喜你,从判断三个点成功转变到判断4个点!
但是大佬不一样,他们直接用一个map,遍历所有点作为顶点,将key设置为边长,将value设置为vector存下所有的相关点,计算长度,因为map的特性,会自动将相同距离的点放在一个vector中。之后只需要用一个双重循环判断vector里面的两点,再加上本身这个顶点。同样是判断三个点的关系,但是这个方法没有做无用功,他重复计算边长的过程大大减少,因为我们计算边长不是为了这个值,而是确定一种关系。
最后稍微解释一下如何判断共线,注意,共线可不只有水平和竖直(别被样例懵逼蒙蔽了双眼),我这里没有计算直线公式,而是根据如果共线,那顶点一定是另外两点的中点,这个关系。(也不一定用距离判断中点,可以直接用坐标~)
AC:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cmath>
using namespace std;
#define ll long long
#define PII pair<ll,ll>
int n;
vector<PII>points;
double dist(PII a, PII b) { return sqrt(pow(a.second - b.second, 2) + pow(a.first - b.first, 2)); }
bool online(double waist, int b, int c) //因为知道是以a为顶点,所以只需要计算bc
{
double b2c = dist(points[b], points[c]);
return abs(2 * waist - b2c) < 1e-6; //涉及到精度问题 其实就是:2 * waist == b2c,如果等于,那就说明共线,返回true
}
int main()
{
cin >> n;
ll a, b;
for (int i = 0; i < n; i++)
{
cin >> a >> b;
points.push_back({ a,b });
}
//将每一个点作为顶点,计算其它点到它的距离
int ans = 0;
vector<unordered_map<double, vector<int> > >dist2point(n);
for (int i = 0; i < n; i++)
{
unordered_map<double, vector<int> >&curmp = dist2point[i]; //当前map存其他点到它的距离
for (int j = 0; j < n; j++)
{
if (i == j) continue;
double dis = dist(points[i], points[j]);
curmp[dis].push_back(j);
}
//现在遍历这张表
for (auto it:curmp) //key是距离,value是点的标号
{
double dis = it.first;
vector<int>pointList = it.second;
for (int p = 0; p < pointList.size(); p++)
for (int q = p + 1; q < pointList.size(); q++)
{
/*cout << i << " " << p << " " << q << endl;*/
if (!online(dis, pointList[p], pointList[q])) //点不重合+不是共线
ans++;
}
}
}
cout << ans;
return 0;
}
酣畅淋漓的二刷:
这次二刷发现不少好东西, 第一次我脑子抽,用一个set记录所有边(为了去重),然后再遍历,只过了80%;
for(int i=0;i<n;i++) //遍历所有点作为等腰三角形顶点
{
map<double,vector<int> >mp;
set<double>edge; //该顶点的所有专属边,用set去重
for(int j=0;j<n;j++) //计算所有点到该顶点的边
{
if(i==j) continue;
double len=getLen(point[i],point[j]);
mp[len].push_back(j);
edge.insert(len);
}
//以该点作为顶点,将边长相同的顶点都拎出来
for(auto l:edge) //遍历该点的所有边长
{
...
}
...
第二次我聪明了一点,将set删了,利用map的key唯一这个特性,相当于自动把我们的边去重了,就把set删了,发现过了95%;
for(int i=0;i<n;i++) //遍历所有点作为等腰三角形顶点
{
map<double,vector<int> >mp;
for(int j=0;j<n;j++) //计算所有点到该顶点的边
{
if(i==j) continue;
double len=getLen(point[i],point[j]);
mp[len].push_back(j);
}
//以该点作为顶点,将边长相同的顶点都拎出来
for(auto it:mp) //遍历该点的所有边长
{
...
}
...
第三次我顿悟了,将map转为unordered_map,然后就过了100%。
也优化了一下判断是否为中点,直接判断坐标即可,也不用计算长度或者计算直线公式什么的。
AC2.0:
//数三角
#include <iostream>
#include <unordered_map>
#include <vector>
#include <cmath>
using namespace std;
struct Node
{
int x,y;
};
int n;
vector<Node>point;
double getLen(Node a,Node b)
{
return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));
}
bool check(int a,int b,int c) //a是顶点,如果不是三角形,那就是中点
{
if(point[a].x*2==point[b].x+point[c].x && point[a].y*2==point[b].y+point[c].y)
return false;
return true;
}
int main()
{
cin>>n;
int x,y;
for(int i=0;i<n;i++)
{
cin>>x>>y;
point.push_back({x,y});
}
int ans=0;
for(int i=0;i<n;i++) //遍历所有点作为等腰三角形顶点
{
unordered_map<double,vector<int> >mp;
for(int j=0;j<n;j++) //计算所有点到该顶点的边
{
if(i==j) continue;
double len=getLen(point[i],point[j]);
mp[len].push_back(j);
}
//以该点作为顶点,将边长相同的顶点都拎出来
for(auto it:mp) //遍历该点的所有边长
{
double l=it.first;
for(int a=0;a<mp[l].size();a++) //遍历该边长下的所有点
for(int b=a+1;b<mp[l].size();b++)
{
if(check(i,mp[l][a],mp[l][b])) //如果构成三角形
{
//cout<<l<<"::"<<i<<" "<<mp[l][a] <<" "<<mp[l][b] <<endl;
ans++;
}
}
}
}
cout<<ans;
return 0;
}