「清华集训 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;
}
posted @ 2019-08-04 10:59  chy_2003  阅读(260)  评论(0编辑  收藏  举报