Bootstrap

【CSP】2023-09-4 阴阳龙 使用set和map降低搜索时间复杂度(本质是使用了二分来优化搜索)

2023-09-4 阴阳龙 使用set和map降低搜索时间复杂度(本质是使用了二分来优化搜索)

题目重述

题目别看这么长,其实几句话就可以把题目大概讲清楚,就是先给你p个位置(是人的位置除非阴阳龙让他们旋转了,他们本身是不会动的),然后给你q个位置(是q个阴阳龙的位置,这个位置也可以看作是不动的),然后找到离每个阴阳龙的位置最近的人的位置(也就是距离最小),然后根据所在阴阳龙的位置旋转坐标,然后最后给出q次后的人的位置。

注意阴阳龙只会影响8个方向上的人。

思路

一开始我是没有优化思路的,就是直接暴力,使用 n 2 n^2 n2的复杂度去搜索,然后发现直接超时,第一个样例就过不了,我们可以发现复杂度最大的就是q里面的搜索离阴阳龙最近的人的位置比较耗时间复杂度是 n n n,但是当时没有想到合适的数据结构来,要是想到用二分搜索,然后使用set这个容器基本上这题就出来了。

其次最巧妙的就是使用了map算是一种离散化方式吧,而不是使用数组,map的索引时间复杂度是 l o g n log n logn而set的二分搜索的复杂度也是 l o g n logn logn 最终的复杂度只有 n l o g n nlogn nlogn 但是需要注意的点也有一些请看下面的介绍

map<int ,int > 

本质上可以看做是一个一维整形数组,比数组的好的地方就是他的索引可以很大,而不占用那么大的连续空间,

这里我们使用了第一个int标识一条线第二个int表示存储位置

map<int , int > mp[4]

这相当于是一个二维数组, // 存储位置和对应 mp[0]行 、mp[1]列 、mp[2]斜线 、mp[3]反斜线

遇到的问题、学习到的知识

set本质是一个红黑树,他自动将元素排序,如果我们看到二分搜索需要每次重新排序的,那么我们可以用set,我们只管往里插,他会自动排好序,并且复杂度是 l o g n logn logn ,然后这题就很适合,旋转的点后需要删除后重新插入然后排序,这里set就发挥作用了,复杂度降低很多。

  1. 使用方法
struct point//定义结构体
{
    int x, y;
    int id;
    point(int x, int y, int id) : x(x), y(y), id(id) {}
    point() {}

} peo[100001];
// 结构体符号重载,以遍set能够排序,注意必须要重载小于号,重载大于号没用
const bool operator<(struct point a, struct point b)
{
    if (a.x == b.x)
        return a.y < b.y;
    return a.x < b.x;
}

map<int, int> mp[4]; // 存储位置和对应  //0行  1列 2斜线 3反斜线
vector<set<point>> points;

  1. 然后我按照上述思路写了以后发现仍然时间超时

原因竟然是我清楚set中的元素是使用的遍历清除

就是

set<point>::iterator it=points[pos].begin();
for(;it!=points[pos].end();it++)
{
	if(it->id==id)
	{
		points[pos].erase(it);
	}
}

这样的复杂度很高

应该使用自带的erase直接删除

    points[pos].erase(people);

这样复杂度只用 l o g n log n logn

  1. 遇到的坑爹问题
    for (int i = 0; i < len; i++)
    {
        if (resDis[i] == toPeo)
        {
            delete_mp(peo[miniDis[i].id]);
            changePoint(toPeo, miniDis[i], u, v, t);
        }
    }
    for (int i = 0; i < len; i++)
    {
        if (resDis[i] == toPeo)
        {
            insert_mp(peo[miniDis[i].id]);
        }
    }

注意这个delete_mp和insert_mp不能在一个循环中,因为set是会自动去重的,如果前面有的点插入到和后面的点相同的位置就被delete了,所以要全delete完才能插入

  1. 遇到的小问题(太蠢了)

在这里插入图片描述

这里u-1 v-1才对

在这里插入图片描述

  1. upper_bound 和 lower_bound 的使用

upper_bound和lower_bound 二分查找

lower_bound 返回的是第一个 大于等于 val 的位置,upper_bound 返回的是第一个大于val的位置

lower_bound(first, end, val)

upper_bound(first, end, val)

例如: 有序数组 1, 5, 9, 11, 11, 13

lower_bound(first, end, 5) - first = 1

upper_bound(first, end, 5) - frist = 2

lower_bound(first, end, 6) - first = 2

upper_bound(first, end, 6) - first = 2

lower_bound(first, end, 11) - first = 3

upper_bound(first, end, 11) - first = 5

完整代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct point
{
    int x, y;
    int id;
    point(int x, int y, int id) : x(x), y(y), id(id) {}
    point() {}

} peo[100001];
// 结构体符号重载
const bool operator<(struct point a, struct point b)
{
    if (a.x == b.x)
        return a.y < b.y;
    return a.x < b.x;
}
int d[8][2] = {{-1, 0}, {-1, -1}, {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}};
map<int, int> mp[4]; // 存储位置和对应  //0行  1列 2斜线 3反斜线
vector<set<point>> points;
int n, m, p, q;
int resDis[8];           // 存储离的最近的哪些点的距离
struct point miniDis[8]; // 存储离的最近的哪些点
int len = 0;             // 存储离的最近的哪些点的长度
int getDistance(int u, int v, int x1, int y1)
{
    int tempd = 0;
    if (x1 == u)
    {
        tempd = abs(y1 - v);
    }
    else if (y1 == v)
    {
        tempd = abs(x1 - u);
    }
    else if (abs(x1 - u) == abs(y1 - v))
    {
        tempd = abs(x1 - u);
    }
    return tempd;
}
void insert_set(int type, int key, struct point people)
{
    if (mp[type].find(key) == mp[type].end() || mp[type][key] == 0)
    {
        set<point> temp;
        temp.insert(people);
        int id = points.size();
        points.push_back(temp);
        mp[type][key] = id;
    }
    else
    {
        int pos = mp[type][key];
        points[pos].insert(people);
    }
}
void delete_set(int type, int key, struct point people)
{
    int pos = mp[type][key];
    points[pos].erase(people);
}
void insert_mp(struct point people) // 要将 people 插入到行、列、斜线和反斜线的set中
{
    insert_set(0, people.y, people);
    insert_set(1, people.x, people);
    insert_set(2, people.y - people.x, people);
    insert_set(3, people.y + people.x, people);
}
void delete_mp(struct point people)
{
    delete_set(0, people.y, people);
    delete_set(1, people.x, people);
    delete_set(2, people.y - people.x, people);
    delete_set(3, people.y + people.x, people);
}
void searchSet(int type, int key, struct point p1)
{
    int pos = mp[type][key];
    if (pos == 0)
        return;
    // 因为一个set代表一个方向,一个方向上的最多有两个点距离最近
    set<point>::iterator l = points[pos].lower_bound(p1);
    set<point>::iterator r = points[pos].upper_bound(p1);
    if (r != points[pos].end() && l != points[pos].begin()) // 如果左右都有数据则取最近的那一个
    {
        l--;
        int r_dis = getDistance(p1.x, p1.y, r->x, r->y);
        int l_dis = getDistance(p1.x, p1.y, l->x, l->y);
        if (r_dis > l_dis)
        {
            miniDis[len++] = *l;
            resDis[len - 1] = l_dis;
        }
        else if (r_dis < l_dis)
        {
            miniDis[len++] = *r;
            resDis[len - 1] = r_dis;
        }
        else // 如果两个距离一样
        {
            miniDis[len++] = *r;
            resDis[len - 1] = r_dis;
            miniDis[len++] = *l;
            resDis[len - 1] = l_dis;
        }
    }
    else if (l != points[pos].begin())
    {
        l--;
        int l_dis = getDistance(p1.x, p1.y, l->x, l->y);
        miniDis[len++] = *l;
        resDis[len - 1] = l_dis;
    }
    else if (r != points[pos].end())
    {
        int r_dis = getDistance(p1.x, p1.y, r->x, r->y);
        miniDis[len++] = *r;
        resDis[len - 1] = r_dis;
    }
}

void changePoint(int k, struct point p1, int u, int v, int t)
{
    int direct;
    int x = p1.x, y = p1.y;
    int pos = p1.id;
    for (direct = 0; direct < 8; direct++)
    {
        if (x == u + k * d[direct][0] && y == v + k * d[direct][1])
        {
            break;
        }
    }
    peo[pos].x = u + k * d[(direct + t) % 8][0];
    peo[pos].y = v + k * d[(direct + t) % 8][1];
}
void solve(int u, int v, int t)
{
    int toBoard = min(min(u - 1, n - u), min(v - 1, m - v));
    int toPeo = -1; // 离人最近的距离
    len = 0;
    // 二分查找离阴阳龙最近的人的位置
    // 将离得最近的那些人的指针放在一个数组中
    struct point temp = point(u, v, 0);
    searchSet(0, v, temp);
    searchSet(1, u, temp);
    searchSet(2, v - u, temp);
    searchSet(3, v + u, temp);

    for (int i = 0; i < len; i++)
    {
        if (toPeo == -1 || resDis[i] < toPeo)
        {
            toPeo = resDis[i];
        }
    }
    if (toPeo > toBoard)
    {
        return;
    }

    for (int i = 0; i < len; i++)
    {
        if (resDis[i] == toPeo)
        {
            delete_mp(peo[miniDis[i].id]);
            changePoint(toPeo, miniDis[i], u, v, t);
        }
    }
    for (int i = 0; i < len; i++)
    {
        if (resDis[i] == toPeo)
        {
            insert_mp(peo[miniDis[i].id]);
        }
    }
    len = 0;
}
int main()
{
    cin >> n >> m >> p >> q;
    points.push_back(set<point>());
    for (int i = 1; i <= p; i++)
    {
        cin >> peo[i].x >> peo[i].y;
        peo[i].id = i;
        insert_mp(peo[i]);
    }
    int u, v, t;
    for (int i = 1; i <= q; i++)
    {
        cin >> u >> v >> t;
        solve(u, v, t);
    }
    ll sum = 0;
    for (int i = 1; i <= p; i++)
    {
        //        cout<<peo[i].x<<' '<<peo[i].y<<endl;
        sum = ((ll)sum) ^ ((ll)i * (ll)peo[i].x + (ll)peo[i].y);
    }
    cout << sum;
    return 0;
}

;