進行中のブラシ紫色の本のタイトル、問題解決シリーズ[ GitHubの | CSDN ]
例8-6 UVA1606両親媒性炭素分子(コードACの43行)
効果の対象に
N座標は(面側の反対側最大に白ドットと黒ドットの数の数を作成する方法、依頼直分割面と、システム、黒または白点をデカルト座標に点を与えられているバー総上のすべての点を追加します)
アイデア解析
分析により、それは仮定することができる直二点の少なくとも一部を介して、状態を変換することによって得ることができない場合、
その後、任意の2点を列挙し、次に残りのn-2点分布、O(N ^ 3)、明らかにタイムアウトの時間複雑さを決定することができます
ポイント選択週の周りの直線、その後、最初の基準点を列挙することができます。たびに線形掃引点に両側の点の数を更新します。極角に応じて並べ替えるためにスキャン前にすべてのポイントので、時間計算量はO(nlogn)、プラス列挙Nの基準点と計算され、時間複雑度はO(N ^ 2logn)であります
このような分析が容易なようだが、実装するための多くの技術があります
座標変換
-
簡略計算:点iは基準点として選択され、算出されたポイントの残りのI(iは座標原点に相当する)、それはその後の計算に便利である相対座標点まで
-
対称性:現在のマップセパレータ製軸対称に黒点相対(xおよびyが取得され、反対番号)場合にのみ、すべての白のドットの数にパーティションの計算側。
これは疑いの参照であってもよい:タイトルは、いずれかの側に黒と白のドットがよくなることができると言うではない、それは、このような黒と白の左のように、状況にカウントするだけではなく、右、黒と白の左右には計算されないのだろうか?
実際、これは我々が各基準点を列挙しているため、手段はラインは、フォームIJとの両方のケースを考えてみJIに表示されることに、発生しません。
極角計算
逆正接関数を使用するとatan2
精度が留意されるべきであるならば、容易に、結果を計算することができます。
両側の更新走査点
この技術は、おそらくほとんどの場所の問題の核心を理解することは困難でなく、スキャン方式であります
最善の方法を理解するために、再びそれを実行するアルゴリズムにいくつかのポイントを描画することです。
まず、作成しi-j,表示点i和j确定的直线
、座標変換した後、実際に、私は座標原点Oに相当するかもしれないので、したがって、分割線はO-i
、統計左/ポイントの上側の定義についてはL1、L1で表され、cnt=2
統計の数、走査線O-j
L2により(離散化手法を表します、)を算出。
L1を回転させたときに第二に、左のポイントは、と少し似以前、新しい統計ポイント(に基づいてL2ながら、CNT- DP /再発思考の、ダブルカウントせずに前に既存の結果の使用、この最適化アルゴリズム)。
これは疑問に見ることができる:L1以上の3点が、結論はまた、それを設定すると?
その答えは、下に示すように、3点があり、このようなラインとして、実際には、分離線の各計算結果に結果を減少させるためのプロセスを設定することであるO-A
分割線の比としてO-B
分割線場合、CNT上のように、ため結果は影響を与えませんので、我々は常に、最大値を取ります
---O---A----B---
- L2が回転しているので、モジュロ演算が使用されるアナログできるように
思考の概要
スキャン:整然とした列挙方法が類似しているが、違いとメンテナンスの重要な量のいくつかの一般的な列挙、計算簡素化すること; DPと再帰的な思考、すでに締結の使用にもやや似は、ダブルカウントを避けるために、最適化アルゴリズム
この問題を提示すると、スキャンラインを維持することであるL2、分割線L1が回転されると、必ずしもすべてスクラッチ計算が、前の位置L2から位置を計算し続け、そしてそれ自体が再-1
ACコード(C ++ 11、極角スキャン、座標変換、対称)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
struct Point{
int x, y, color;
double theta; // 相对于基准点的极角;acrtan计算
}p[maxn], pt[maxn]; // p:原数据;pt:变换后的坐标
int n;
bool isLeft(const Point& a, const Point& b) { // O-a为分隔线,判断b是否在O-a上侧
return a.x * b.y - a.y * b.x >= 0; // 直线方程判断
}
int solve() {
if (n <= 3) return n; // 1/2/3直接返回
int ans=0;
for (int i=0; i < n; i ++) { // 枚举n个基准点
int k=0;
for (int j=0; j < n; j ++) { // 相对坐标变换(将i作为j的原点)
if ( i == j) continue; // 同一个点跳过
pt[k].x = p[j].x - p[i].x; // 求点j相对于基准点i的坐标
pt[k].y = p[j].y - p[i].y;
if (p[j].color == 1) {pt[k].x = -pt[k].x; pt[k].y = -pt[k].y;} // 将黑色点对称变换,到时候只需扫描180即可
pt[k].theta = atan2(pt[k].y, pt[k].x); // 利用反正切求角度
k ++;
}
sort(pt, pt+k, [](Point& a, Point& b) {return a.theta < b.theta;}); // 按照极角升序排列
int cnt=2, pcur=0, prot=0; // pcur:当前分隔线,prot:旋转线
while (pcur < n-1) { // 所有分隔线
if (pcur == prot) {prot = (prot+1)%(n-1); cnt ++;} // 后面扣除
while (pcur != prot && isLeft(pt[pcur],pt[prot])) {prot = (prot+1)%(n-1); cnt ++;} // 只计数一侧,因为之前黑色点变换过
cnt --; // 前面多加一次
ans = max(ans, cnt);
pcur ++; // 下一个分隔线
}
}
return ans;
}
int main() {
while (scanf("%d", &n) == 1 && n != 0) {
for (int i=0; i < n; i ++) scanf("%d%d%d", &p[i].x, &p[i].y, &p[i].color);
printf("%d\n", solve());
}
return 0;
}