题解 CF1163C1 Power Transmission (Easy Edition)

传送门

题意描述

平面上有 \(n\) 个点,每两个点之间有一条直线,求有多少对直线相交。

注意:

  • 若有多条直线完全重合,则他们都算是同一条一条直线;
  • 求的是有多少对直线相交,而不是有多少个交点。

思路分析

一条直线可以表现为:\(y=kx+b\)

由初中知识可得:

  • 两条直线不是相交就是平行;
  • 平行 \(\Leftrightarrow\) \(k\) 相等。

因此,题目就可以转化为:有多少对直线的 \(k\) 不相等。

方法解析

如何求 \(k\)

由初中知识得:

  • \(k\) 即为斜率;
  • 斜率 \(=\) 纵坐标之差 \(\div\) 横坐标之差。

具体地,若有两个点 \(a,b\),则 \(k=(x_a-x_b)\div(y_a-y_b)\)

另外,由于 \((x_a-x_b)\div(y_a-y_b)=(x_b-x_a)\div(y_b-y_a)\),因此 \(k\)\(a,b\) 两点的相对位置无关,即无需考虑 \(a,b\) 之间的位置关系,直接算就好

接下来考虑一个问题:\(k\) 不一定是整数,而此题要求比较两个 \(k\) 是否严格相等,使用 double 大概率会碰上精度的问题—— 比如\(\frac{1}{9999}\)\(\frac{1}{10000}\),因此我们考虑以下算法:

定义两个变量 \(k\_son\)\(k\_mom\),分别表示 \(k\) 的分子与分母,那么 \(k\) 便可以表示为 \(\frac{k\_son}{k\_mom}\),我们只要把 \(k\) 约分,那么便可以通过 \(k\_son\)\(k\_mom\) 精准表示每一个 \(k\)


注意点:

  • 由于我们直接计算 \(k\),可能会出现 \(k=\frac{-1}{2}\)\(k=\frac{1}{-2}\) 的情况,它们两个是同一个 \(k\) 但是 \(k\_son\)\(k\_mom\) 并不相等。因此我们应该维护 \(k\_mom\) 恒大于 \(0\),这样子就不会在比较时出现问题;
  • 当直线平行于 \(y\) 轴时,我们可以令 \(k\_son=1,k\_mom=0\),与其他直线区分开来;
  • 当直线平行于 \(x\) 轴时,我们应当令 \(k\_son=k\_mom=0\),防止出现类似于 \(\frac{0}{1}\neq\frac{0}{2}\)的问题。

对于 \(b\)

\(b\) 能用来干什么?\(b\) 能用来判重合的直线。

由于我们能通过 \(y=kx+b\) 精准表示每一条直线,因此只要两条直线的 \(k,b\) 都相等,它们就是同一条直线。

\(b=y-kx\),由于 \(k\) 被我们用分数表示,我们可通过先通分,再约分的流程求出 \(b\)

但是!由于本人太菜了在写题解的时候才想到了这个方法,因此我们再说说另一个办法!


如何判重

显然我们可以用 \(k,b\) 判重,但是我们现在来考虑另一种做法。

我们回到初中数学,如下图:

cWayqK.png

如何判断三点共线?

\(\because AB//BC\)

\(\therefore A,B,C\) 共线

相信你的数学老师是不会给你错的!

如果用人话讲就是:若两条平行线段有公共端点,那么它们共线。

带入到题目中,由于题目每两个点直接都连一条直线的性质,若是我们把直线变为线段,那么一条线段一定和另一条与它共线的线段有公共端点(如果有的话)。

我们定义 \(x_i, y_i\) 表示 \(i\) 号直线的两个原端点,如果有两条直线有一个原端点重合并且这两条直线平行,那么它们共线。

我们可以用dfs来解决此问题。

AC代码

说了这么多,上代码!

#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
using namespace std;

const int N = 51;
struct Node
{
    int k_mom, k_son, x, y;
}line[1000000];

struct Spot
{
    int x, y;
}a[1000000];

bool z[1000000];
int len;

int gcd(int a, int b)
{
    if(b == 0) return a;
    return gcd(b, a % b);
}

void dfs(int u)//dfs判重
{
	z[u] = true;
	for(int i = 1; i <= len; i ++)
	{
		if(!z[i] && (line[i].x == line[u].x || line[i].x == line[u].y || line[i].y == line[u].y || line[i].y == line[u].x) && line[i].k_son == line[u].k_son && line[i].k_mom == line[u].k_mom)
		{
			dfs(i);
		}
	}
}

int main()
{
    int n, x, y, x1, y1, ans = 0;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++)
    {
        scanf("%d %d", &a[i].x, &a[i].y);
    }

    for(int i = 1; i <= n; i ++)//生成直线
    {
        if(z[i]) continue;
        for(int p = 1; p < i; p ++)
        {
            if(z[p] || (a[i].x == a[p].x && a[i].y == a[p].y)) continue;

            if(a[i].x == a[p].x)
            {
                line[++ len].k_son = 0;
                line[len].k_mom = 0;
                line[len].x = i, line[len].y = p;
            }
            else if(a[i].y == a[p].y)
            {
                line[++ len].k_son = 1;
                line[len].k_mom = 0;
                line[len].x = i, line[len].y = p;
            }
            else
            {
                x = a[i].x - a[p].x, y = a[i].y - a[p].y;
                line[ ++ len].k_son = x / gcd(abs(x), abs(y));
                line[len].k_mom = y / gcd(abs(x), abs(y));
                line[len].x = i, line[len].y = p;

				if(line[len].k_mom < 0)//维护k_mom恒大于0
				{
					line[len].k_mom *= -1;
					line[len].k_son *= -1;
				}
            }
        }
    }

    for(int i = 1; i <= len; i ++)//判重
    {
    	if(!z[i])
		{
			dfs(i);
			z[i] = false;
		}
	}
	
    for(int i = 1; i <= len; i ++)//计算答案
    {
    	if(z[i]) continue;
        for(int p = 1; p < i; p ++)
        {
        	if(z[p]) continue;
            if(line[i].k_mom != line[p].k_mom || line[i].k_son != line[p].k_son)
            {
                ans ++;
            }
        }
    }
	
    cout << ans << endl;

    return 0;
}

Written by \(\operatorname{Rosmarinus}\).

希望能对您有所帮助。

posted @ 2021-04-16 13:23  XJ21  阅读(81)  评论(0)    收藏  举报