画圆的沙滩

亦简亦美

平面的切割

这是一个有趣的问题。编程之美1.7节进行了探讨。其中解法二的思路非常巧妙。其用到的找逆序数算法是merge sort的变形,也很常见。最后的代码是这个算法的一份实现。

这里则要从一个不同的角度来考虑这个问题。作者在分析分割的区域时,得到了这样的公式: F = N+M+1,其中N为直线数,M为交点数。作者的思路简明清晰。这里则要采用另外一个思路来给出这个公式。

考虑著名的欧拉公式:F = E - V + 2。我们可以假想两条垂线在无穷远处重合,从而构成一个长轴是无穷大的椭圆区域,如下面的图所示。只考虑区域内的线段,再将区域外想象成为一个面,则整个图形构成一个压平的多面体。其顶点就是这些线条的交点,边就是交点分割出来的线段,分割出来的区域就是面。于是,我们要求的分割区域数即F - 1 = E - V + 1。

现在,仍然假设已知直线数N和交点数M,试计算E和V,从而获得F。对于V,除了交点还有直线与边界的两个交点。故有 V = M + 2N。再考虑E。对于每一个交点,都有4条边与之对应。处于交点之间的边均被两边的交点各计算一次,共两次,而边缘的边仅计算了一次。另外,E中还包含边界上分割而成的边。边界上共有2N条边,边缘边也共2N条,从而交点边共E-4N条。根据上面交点和边的关系有:2(E-4N) + 2N = 4M,即E = 2M + 3N。带入欧拉公式即得F = N+M+1。

注意以上的推理过程并没有关于直线不能与Y平行的约束。所以即使有这样的直线,结果也是成立的。

template<class It>
size_t count_inversions(It first1, It last1, It first2, It last2) {
size_t r
= 0;
while (first1 != last1 && first2 != last2)
if (*first1 <= *first2) ++first1;
else r += distance(first1, last1), ++first2;
return r;
}

template
<class It>
size_t count_inversions(It first, It last) {
size_t d = distance(first, last);
if (d < 2) return 0;

It middle
= first;
advance(middle, d
/2);
size_t r
= count_inversions(first, middle) + count_inversions(middle, last)
+ count_inversions(first, middle, middle, last);
inplace_merge(first, middle, last);

return r;
}

int main() {
int n;
while (cin>>n) {
if (!n) break;
vector
<int> vec;
while (n--) *back_inserter(vec) = *istream_iterator<int>(cin);
cout
<<count_inversions(vec.begin(), vec.end())<<'\n';
}
return 0;
}

posted on 2011-03-23 18:23  acmaru  阅读(301)  评论(0编辑  收藏  举报

导航