指向三角形 - SPOJ任务

时间:2014-07-06 17:14:27

标签: perl

我正在尝试通过回答波兰SPOJ(Sphere Online Judge)网站的一些问题来学习Perl。这是关于541: Points Within Triangles问题。我会尽力翻译。

我必须找出点p =(x,y)是否在由其三个顶点定义的三角形内。

三角形的坐标是:p1 =(x1; y1),p2 =(x2; y2),p3 =(x3; y3)。 x1,y2,x3等中的每一个都是(1 ... 1000)范围内的整数。

每行输入为8个整数:

x1 y1 x2 y2 x3 y3 x y
 .....
0 0 0 0 0 0 0 0

如果所有八个值都为零,那么程序应该终止。

每行输入应该生成一行包含单个字母的输出:

  • ,如果该点位于三角形内
  • O ,如果该点位于三角形之外
  • E ,如果该点位于三角形上

这是我的代码:

use warnings;
use strict;

our ($a, @t, $q);
our ($x1, $x2, $x3, $y1, $y2, $y3, $px, $py);
our ($det);
our ($deta, $detb, $detc);

$a = 0;

sub licz {
  ($t[0][0],$t[0][1],$t[1][0],$t[1][1],$t[2][0],$t[2][1])=@_;

  my $ret = (
    $t[0][0]*$t[1][1]+
    $t[1][0]*$t[2][1]+
    $t[2][0]*$t[0][1]-
    $t[2][0]*$t[1][1]-
    $t[0][0]*$t[2][1]-
    $t[1][0]*$t[0][1]
  );

  return ($ret);
}

sub czek {
  $deta = licz($x1, $y1, $x2, $y2, $px, $py);
  $detb = licz($x2, $y2, $x3, $y3, $px, $py);
  $detc = licz($x3, $y3, $x1, $y1, $px, $py);
  if ($deta == 0 or $detb == 0 or $detc == 0) {
    return 1;
  }
  else {
    return 0
  }
}

sub kier {
  if ($det > 0) {
    return 1;
  }
  else {
    return 0;
  }
}

while () {
  my @tab = split(/\s+/, <>);
  if ( $tab[0] and $tab[1] and $tab[2] and $tab[3] and $tab[4] and $tab[5] and $tab[6] and $tab[7] ) {
    $x1  = $tab[0];
    $y1  = $tab[1];
    $x2  = $tab[2];
    $y2  = $tab[3];
    $x3  = $tab[4];
    $y3  = $tab[5];
    $px  = $tab[6];
    $py  = $tab[7];
    $det = licz($x1, $y1, $x2, $y2, $x3, $y3);

    if (czek()) {
      print("E\n");
    }
    else {
      if (kier()) {
        if ($deta > 0 and $detb > 0 and $detc > 0) {
          print("I\n");
        }
        else {
          print("O\n")
        }
      }
      else {
        if ($deta < 0 and $detb < 0 and $detc < 0) {
          print("I\n")
        }
        else {
          print("O\n");
        }
      }
    }
  }
  else {
    last
  }
}

我不明白为什么我不能一直得到AC(WA)。我测试的所有输入都有很好的答案,所以我认为这是一些空间或类似的问题。如果有人可以帮助我,请做。

2 个答案:

答案 0 :(得分:1)

其他答案中的工作算法

我已经在我的earlier answer中展示了针对此问题的工作算法。但是,您也在询问为什么不接受您的代码作为答案。

逻辑失败的示例

代码对以下数据失败的一个示例:

1 1 4 1 4 4 5 5

(5,5)将被错误地报告为在边缘而不是在以下三角形之外:

Triangle (1,1);(4,1);(4,4) Point (5,5)

基本上,边缘创建的矢量上的任何点都会实际报告在边缘上。因此,以下几点也会在边缘报告:(500, 500)(4, -9000)(0,0)等。

为了证明你的逻辑是失败的,有必要清理你的代码。

编码样式修复

感谢您加入use strict;use warnings;。这是成为perl的优秀程序员的第一步。

但是,下一步是永远不要将全局变量用于除常量之外的任何变量。 始终将参数传递给子程序并返回值。当然可以通过其他方式进行编码,但将每个子程序保持为孤立的思想或想法有助于提高可读性和可维护性。

应用这一种单一编码风格可将您的程序减少到以下内容:

use warnings;
use strict;

sub licz {
    my ($x1, $y1, $x2, $y2, $x3, $y3) = @_;
    return ($x1*$y2 - $x2*$y1) + ($x2*$y3 - $x3*$y2) + ($x3*$y1 - $x1*$y3);
}

while (<DATA>) {
    my @tab = split(/\s+/, $_);
    last if grep {!$_} @tab[0..7];

    my ($x1, $y1, $x2, $y2, $x3, $y3, $px, $py) = @tab;

    my $det = licz($x1, $y1, $x2, $y2, $x3, $y3);
    my $deta = licz($x1, $y1, $x2, $y2, $px, $py);
    my $detb = licz($x2, $y2, $x3, $y3, $px, $py);
    my $detc = licz($x3, $y3, $x1, $y1, $px, $py);

    if ($deta == 0 || $detb == 0 || $detc == 0) {
        print("E\n");
    }
    elsif ($det > 0) {
        if ($deta > 0 and $detb > 0 and $detc > 0) {
            print("I\n");
        }
        else {
            print("O\n")
        }
    }
    else {
        if ($deta < 0 and $detb < 0 and $detc < 0) {
            print("I\n")
        }
        else {
            print("O\n");
        }
    }
}

__DATA__
630 421 326 242 561 432 478 332
378 212 380 550 840 735 379 381
591 916 765 191 487 490 678 554
975 75 324 166 343 28 650 120
1 1 4 1 4 4 500 500
1 1 1 4 4 4 5 5
0 0 0 0 0 0 0 0

逻辑中的缺陷

现在,正如您所看到的,您的程序看起来与我的程序非常相似。事实上,我们使用相同的公式。你的只是矢量交叉乘积的简化形式,我从点减法开始。您($deta, $detb, $detc)计划中的值相当于我的($cps[0], $cps[1], $cps[2])

这些值告诉我们一个点是在右边,左边还是在边缘。他们通过价值的标志来做到这一点。您还可以使用$det计算三角形的顺时针或逆时针特性。但是,您不需要此信息。

基本上,如果我们围绕三角形的3个边缘,并且每个边缘的右边都有一个点,则该点必须位于三角形的内部。类似地,如果我们绕过3个边,并且每个边的左边都有一个点,那么我们也可以得出结论,该点必须在三角形内,并且我们必须逆时针遍历三角形。最后,每个交叉产品的标志并不重要,只是标志是否匹配。

  • 交叉产品的符号全部匹配⇒点在三角形内
  • 正负号⇒点在三角形之外

现在,如果交叉积的符号为零,我们知道该点是沿着边缘,但是这些信息不足以确定该点是否实际上是在边缘。有五种不同的情况。我将从明显的开始:

  1. 如果两个交叉积为零⇒点匹配三角形的顶点
  2. 如果非零交叉积均为正⇒点位于边缘
  3. 如果非零交叉积均为负值⇒点位于边缘
  4. 如果有正负交叉产品⇒点在边缘但在外面。
  5. 现在,希望这揭示了你逻辑中的缺陷。基本上,您检查交叉产品是否为零,但是您没有检查其他交叉产品的标志。事实上,人们可以而且应该只是检查点是否在外面,然后再去检查它是否在边缘,如我的解决方案所示。

    我现在已经准确地列出了修复代码所需要做的事情,但我将最终修改留给您。

答案 1 :(得分:0)

使用Point Inside or Outside Triangle?作为指导,以下内容使用向量叉积确定三角形内部,外部或边缘的点:

#!/usr/bin/perl
use strict;
use warnings;

while (<DATA>) {
    last if !/[1-9]/;
    #print;
    my ($x1, $y1, $x2, $y2, $x3, $y3, $x, $y) = split;

    my @cps = (
        ($x1 - $x2) * ($y1 - $y) - ($x1 - $x) * ($y1 - $y2),
        ($x2 - $x3) * ($y2 - $y) - ($x2 - $x) * ($y2 - $y3),
        ($x3 - $x1) * ($y3 - $y) - ($x3 - $x) * ($y3 - $y1),
    );

    # Both Negative and Positive Cross Products means is outside
    if (grep {$_ > 0} @cps and grep {$_ < 0} @cps) {
        print "   Outside\n";

    # A Zero Cross Product means is on an edge as long as the sign of all other CP's match
    } elsif (my $zeros = grep {$_ == 0} @cps) {
        print $zeros == 1
            ? "   Edge\n"
            : "   Vertice\n";

    # Same Sign for all CP's means is Inside.
    } else {
        print "   Inside\n";
    }

__DATA__
630 421 326 242 561 432 478 332
378 212 380 550 840 735 379 381
591 916 765 191 487 490 678 554
975 75 324 166 343 28 650 120
0 0 0 0 0 0 0 0

输出:

   Inside
   Edge
   Outside
   Inside