蓝桥杯2023(十四届)国赛——数三角(map)

3.数三角 - 蓝桥云课 (lanqiao.cn)

这道题考试的时候,认准暴力!可过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;
 } 

  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值