分区飞盘 C++

Partition Frisbees C++

我们有一组 F 的 n 个二维飞盘。我们想将 F 划分为两个子集 F1 和 F2,以便在每个子集中没有两个飞盘相交。我们的函数接受这样的输入:(x_j, y_j) 是第 j 个飞盘的中心,rad_j 是第 j 个飞盘的半径。输出应该是 s_0 s_1 ... s_n-1,其中 s_j = 1 如果第 j 个飞盘在 F1 中,s_i = 2 如果第 j 个飞盘在 F2 中。如果不能对 F 进行分区,只需 return 0。理想情况下,算法应该在 O(n^2) 时间内计算。

我认为我应该使用某种类型的矩阵表示形式来表示这种图形,但后来我认为我不需要构建图形,但我认为我 BFS/DFS 会很有用,但我坚持如何在 O(n^2) 中优雅地做到这一点。顺便说一句,我正在用 C++ 编写代码。

难道你不应该只做一个堆叠的 for 循环并使用距离公式吗?即,如果它们的中心之间的距离小于它们的半径之和,则两个相交。

在那之后,你已经对其进行了状态分解,然后你可以继续执行循环 inclusion/exclusion(即包含所有内容并删除所有无效的,然后包含尽可能多的有效内容等)

您的图形搜索方向正确。这是一个 C++11、O(V^2)、使用 O(V+E) space 的深度优先搜索解决方案。

DFS 本​​身的时间复杂度为 O(V+E),但生成邻接表的时间复杂度为 O(V^2)。

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

struct Frisbee
{
  double x;
  double y;
  double radius;
};

int dfs(const vector< vector<int> > &adj, vector<int> &p, int last_ind, int curr_ind)
{
  if (p[curr_ind])                   // node already painted
  { 
    if (p[last_ind] == p[curr_ind])  // painted same color as neighbor -> failure
      return 0;

    return 1;                        // painting is compatible
  }

  // node not yet painted

  p[curr_ind] = (1 == p[last_ind] ? 2 : 1);        // paint opposite color as neighbor

  for (int j = 0; j < adj[curr_ind].size(); ++j)
    if (!dfs(adj, p, curr_ind, adj[curr_ind][j]))  // dfs on neighbors
      return 0;

  return 1;
}

int partition(const vector<Frisbee> &F, vector<int> &p)
{
  // compute adjacency lists

  vector< vector<int> > adj(F.size());

  p.resize(F.size());

  for (int i = 0; i < F.size(); ++i)
  {
    p[i] = 0;

    for (int j = i + 1; j < F.size(); ++j)
    {
      double dist = sqrt((F[i].x - F[j].x) * (F[i].x - F[j].x) + (F[i].y - F[j].y) * (F[i].y - F[j].y));

      if (dist < F[i].radius + F[j].radius)
      {
        adj[i].push_back(j);
        adj[j].push_back(i);
      }
    }
  }

  // find starting points for dfs

  for (int i = 0; i < F.size(); ++i)
    if (0 == p[i])                       // node i not yet painted
    {      
      p[i] = 1;                          // arbitrarily choose initial color

      for (int j = 0; j < adj[i].size(); ++j)
        if (!dfs(adj, p, i, adj[i][j]))  // dfs on neighbors
          return 0;
    }

  return 1;
}

int main(int argc, char **argv)
{
  vector<Frisbee> F = { { 1.0, 1.0, 1.0 }, { 2.0, 2.0, 1.0 }, { -1.0, -1.0, 1.0 }, { -2.0, -2.0, 1.0 }, { 5.0, 5.0, 1.0 }, { -5.0, 5.0, 1.0 } };
  vector<int>     p;

  if (partition(F, p))
  {
    for (size_t i = 0; i < F.size(); ++i)
      cout << p[i] << " ";

    cout << endl;
  }
  else
    cout << "No partition possible!" << endl;

  F.push_back({ 1.5, 1.5, 1.0 });  // add a 3-way intersection

  if (partition(F, p))
  {
    for (size_t i = 0; i < F.size(); ++i)
      cout << p[i] << " ";

    cout << endl;
  }
  else
    cout << "No partition possible!" << endl;


  return 0;
}

这是输出(在略有不同的飞盘组上的两个分区):

1 2 1 2 1 1 
No partition possible!

您可以构建一个边表示 "touches" 的图。然后您可以在该图上使用二分算法。 Boost.Graph 包含一个。

http://www.boost.org/doc/libs/1_57_0/libs/graph/doc/is_bipartite.html

该算法是 O(V+E),即如果所有圆盘相互接触,则最坏情况为 O(V^2)(尽管在这种情况下它很可能会提前中止)。

天真地构建图形是 O(V^2),因为您必须将每个圆盘与所有其他圆盘进行比较,尽管您可以通过构建地理四叉树首先对圆盘进行排序来优化常见情况。