「清华集训 2017」我的生命已如风中残烛
问题分析
题意不难理解。只是为什么绳子固定的那个端点不算一个钉子
然后没有什么想法,于是就考虑暴力。不难发现,每次找到一个圈之后,长度就会剩下不到一半。于是至多找\(\log L\)次圈。每次找圈可能找到\(O(n)\)个点,找下一个点需要\(O(n)\)的时间。于是得到一个虚假的总时间复杂度是\(O(Tm\log L n ^2 )\)。\(3e10\)感觉\(10\)秒不太行。
然后考虑次数最多的操作进行优化,就是加速找下一个点。首先预处理以每个点为中心的极角排序,之后先二分查找极角再扫下一个点,这样能大大加速找点的时间。虽然时间复杂度上限应该没有变,但是不难发现这个上限已经很松了。然后就勇一发。然后总时间只有1700+ms?单个点最慢只有400+ms?这就没了?
下面是优化建议,本人没有实现过:经实测上述暴力加上第二点优化总时间在1500+ms
1、开始的时候直接预处理点\(x\),极角为\(\alpha\),正好碰到点\(y\)的长度。这样开始时一个很松的\(O(n^3)\)预处理,然后后面每次查找只需要\(O(\log n)\)即可。这样时间复杂度为\(O(Tn^3+Tm\log L n \log n)\)。大概在\(2e9\)。上限比直接暴力大概能少一个数量级。
2、对于经常用到的长度、距离函数开数组存储,属于常数优化。
参考程序
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
void Main();
int main() {
int TestCases;
scanf("%d", &TestCases);
for (; TestCases--;) Main();
return 0;
}
const int Maxn = 510;
const double Eps = 1e-12;
inline int Cmp(const double x, const double y) {
if (fabs(x - y) <= Eps)
return 0;
if (x - y > Eps)
return 1;
return -1;
}
struct point {
double x, y;
point() {}
point(const double _x, const double _y) : x(_x), y(_y) {}
inline void Read() {
scanf("%lf%lf", &x, &y);
return;
}
inline point operator-(const point Other) const { return point(x - Other.x, y - Other.y); }
inline double operator*(const point Other) const { return x * Other.y - Other.x * y; }
inline point operator*(const double Other) const { return point(x * Other, y * Other); }
inline double Len() const { return sqrt(x * x + y * y); }
};
struct member {
int Quadrant, Index;
point Point;
member() {}
member(const int _Index, const point _Point) {
Index = _Index;
Point = _Point;
Quadrant = (Cmp(Point.y, 0.0) != 0) ? Cmp(Point.y, 0.0) : Cmp(Point.x, 0.0);
return;
}
inline bool operator<(const member Other) const {
if (Quadrant != Other.Quadrant)
return Quadrant < Other.Quadrant;
double Temp = Cmp(Point * Other.Point, 0.0);
return Temp < 0 || Temp == 0 && Point.Len() > Other.Point.Len();
}
};
int n, m;
point Point[Maxn];
member Relation[Maxn][Maxn];
int RelationSize[Maxn];
point Start, Direction;
double Length;
int Stack[Maxn], Vis[Maxn][Maxn];
void Init();
int Work();
void Main() {
Init();
for (int i = 1; i <= m; ++i) printf("%d\n", Work());
return;
}
void Init() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) Point[i].Read();
for (int i = 1; i <= n; ++i) {
RelationSize[i] = 0;
for (int j = 1; j <= n; ++j)
if (i != j)
Relation[i][++RelationSize[i]] = member(j, Point[j] - Point[i]);
sort(Relation[i] + 1, Relation[i] + RelationSize[i] + 1);
}
return;
}
int Find(const int Index, const point Direction);
int Work() {
Start.Read();
Direction.Read();
scanf("%lf", &Length);
Direction = Direction - Start;
Direction = Direction * (Length / Direction.Len());
for (int i = 1; i <= n; ++i) Relation[0][i] = member(i, Point[i] - Start);
RelationSize[0] = n;
sort(Relation[0] + 1, Relation[0] + n + 1);
int First = Find(0, Direction);
if (First == -1)
return 1;
Length -= Relation[0][First].Point.Len();
Direction = Relation[0][First].Point * (Length / Relation[0][First].Point.Len());
First = Relation[0][First].Index;
int Second = Find(First, Direction);
if (Second == -1)
return 2;
Length -= Relation[First][Second].Point.Len();
Direction = Relation[First][Second].Point * (Length / Relation[First][Second].Point.Len());
Second = Relation[First][Second].Index;
int Ans = 2;
for (int Time = 1;; ++Time) {
Stack[0] = 0;
Stack[++Stack[0]] = First;
Stack[++Stack[0]] = Second;
for (;;) {
int Next = Find(Second, Direction);
if (Next == -1)
return Ans + Stack[0] - 1;
Length -= Relation[Second][Next].Point.Len();
Direction = Relation[Second][Next].Point * (Length / Relation[Second][Next].Point.Len());
Next = Relation[Second][Next].Index;
Stack[++Stack[0]] = Next;
First = Second;
Second = Next;
if (Vis[First][Second] == Time)
break;
Vis[First][Second] = Time;
}
--Stack[0];
int Pos;
for (Pos = 0; Pos < Stack[0]; ++Pos)
if (Stack[Pos] == First && Stack[Pos + 1] == Second)
break;
double TotalLength = 0;
for (int i = Pos; i < Stack[0]; ++i) TotalLength += (Point[Stack[i + 1]] - Point[Stack[i]]).Len();
Ans += Stack[0] - 1;
int Times = (int)floor(Length / TotalLength);
Ans += Times * (Stack[0] - Pos);
Length -= TotalLength * Times;
Direction = Direction * (Length / Direction.Len());
}
return Ans;
}
int Find(const int Index, const point Dir) {
member Direction = member(0, Dir);
int Size = RelationSize[Index];
int Ans = lower_bound(Relation[Index] + 1, Relation[Index] + Size + 1, Direction) - Relation[Index];
if (Ans > Size)
Ans = 1;
for (int i = 1; i <= Size; ++i) {
if (Cmp(Dir.Len(), Relation[Index][Ans].Point.Len()) == 1)
return Ans;
++Ans;
if (Ans > Size)
Ans = 1;
}
return -1;
}