难度:β
建议用时:40 min
实际用时:1 h
题目:??
代码:??
这题我看了刘汝佳大神的代码,在上面改了几个变量的名称,方便理解。
这是一道简单的几何题(不是真正的集合题,更可以说是一个坐标题),所以长话短说了。
题目告诉我们有一些随机分布的点,要我们用一个隔板把平面分开,然后往两边分别倒入水和丙酮来溶解掉这些点。
然而这些点有的只能溶解在水里,有的只能溶解到丙酮里。
任务是找到最大的溶解的点的数量。
这题建模就是按照刘汝佳大神的方法,一个隔板分开平面,要使左侧黑点数加右侧白点数的和最大。找到最大值。
具体的算法就不讨论了,书上有。
这里说一下大神的代码里的细节。
首先弄一个结构体,存点的相对坐标与相对角。
相对谁呢?相对枚举的原点。
for (int pivot = 0; pivot < n; pivot++)
以这个枚举的点为原点建立坐标系,表示每个点的坐标和与 x 轴正半轴的夹角。
for (int p = 0; p < n; p++) { if (p == pivot) continue; rlt_pos[tot].x = abs_pos[p].x - abs_pos[pivot].x; rlt_pos[tot].y = abs_pos[p].y - abs_pos[pivot].y; if (color[p] == 1) { rlt_pos[tot].x = -rlt_pos[tot].x; rlt_pos[tot].y = - rlt_pos[tot].y; } rlt_pos[tot].rad = atan2(rlt_pos[tot].y, rlt_pos[tot].x); tot++; }
这里我把大神的代码里面的一些变量名称改了一下,便于我自己理解。
注意到大神有一个方便的做法。他把右侧的黑点当作左侧的白点,然后下面只统计左侧的点(不管颜色)有几个(而不是先统计左侧的白点又统计右侧的黑点)。这样统计时更快更便捷。
然后把每个点按相对夹角排序,这样可以保证一个一个扫描。
接下来就是重点了。扫描。
这个扫描的步骤我看了半天才明白。
其大致想法是:对每一个分割线,统计左边点的个数,然后让扫描到的点标号不变,改变分割点,继续从上次扫描的地方开始扫描,计数。
这样的好处是,不用每次改变分割线后重新从分割线开始扫描。前面的复杂度是 n^2,优化后是 n。
1 int L = 0, R = 0, cnt = 2; // 初始时记扫描点为 L,分割点为 R。 2 while (R < tot) { 3 if (L == R) { L = (L+1)%tot; cnt++; } // 如果扫描点与分割点重合,计数加一 4 while (L != R && left(rlt_pos[L], rlt_pos[R])) { L = (L+1)%tot; cnt++; } // 扫描 5 cnt--; // 减掉上次的分割点 6 R++; // 分割点改变 7 ans = max(ans, cnt); 8 }
这里比较难想的是第 3 行和第 5 行。
不难发现,第 3 行只在第一次扫描时执行。因为之后扫描点与分割点不可能重合了,这是因为我们要求扫描点必须要在分割点左边。
即使在某次扫描时扫描点刚好转到了分割点(然后退出第 4 行),然而分割点每次加 1,这样扫描点依然追不上分割点。
第 5 行。这里转完以后统计时要减 1,减的是上次的分割点。然而对于第一次扫描是不存在“上一个”分割点的,所以 cnt 的初始值设为 2。cnt 代表当前确定的点数。cnt = 1 是原点。
大神的代码让我看的花方。
2018-02-03