CSP-阴阳龙 满分题解(从15分debug到100的心路历程)

题目回顾:

阴阳龙icon-default.png?t=N7T8https://sim.csp.thusaac.com/contest/31/problem/3

C++满分题解

思路概述

        从输入到输出,整理逻辑为:

        接下来设计数据结构:

        首先需要一个结构来存放员工和阴阳龙位置:

struct location {
    long long x;      //横坐标
    long long y;      //纵坐标
    int pID;          //员工编号,阴阳龙编号为0

    location(long long x = 0, long long y = 0, int pID = 0) {
        this->pID = pID;
        this->x = x;
        this->y = y;
    }

};

        求解k时,刚开始我朴素的定义了一个函数来计算两个location之间的距离:

long long l_distance(location l1, location l2){
    if(l1.x==l2.x)                                 //在x轴方向
        return abs(l1.y-l2.y)==0? 1e10:abs(l1.y-l2.y);
    else if(l1.y==l2.y)                            //在y轴方向
        return abs(l1.x-l2.x);
    else if(abs(l1.x-l2.x) == abs(l1.y-l2.y))      //在对角线方向
        return abs(l1.x-l2.x);
    else return 1e10;
}

        于是,我朴素的进行如下操作求解,这样一套操作完成,时间复杂度为: O(pq) 

#伪代码
1. 输入,初始化地图和人员
2. 阴阳龙开始刷新

        3. 计算阴阳龙刷新位置到所有人员的最短距离 (d1)

        4. 计算阴阳龙到边界的最短距离 (d2)
        5. k = d1<d2 ? d1:0
        6. 更新人员
7. 计算最终结果
end

        提交之后发现只通过了用例2,剩下的都是运行超时(用例2的q最小)。

       于是开始改进。可以发现,在计算阴阳龙刷新位置到人员的最短距离时,并不是所有的人员都需要参与计算,只需要考虑八个方向上的点即可,这是简化之一。同时,在查找一个方向(数轴)上距离某位置最近的左右两侧的点时,进行二分查找可以将时间复杂度降低,这是简化之二:

 T(p') = 1+T(\frac{p'}{2})=2+T(\frac{p'}{4}) = ... = log_{2}p'+T(\frac{p'}{p'})

        由于简化之一,p'<p,所以简化后时间复杂度为: O(p*log_{2}q')

        更新后的思路为:

#伪代码
1. 输入,初始化地图和人员
2. 阴阳龙开始刷新

        3. 计算阴阳龙8个方向的人员的最短距离 (d1)

        4. 计算阴阳龙到边界的最短距离 (d2)
        5. k = d1<d2 ? d1:0
        6. 更新人员
7. 计算最终结果
end

        同时,也需要更新数据结构来实现这一需求。C++ set 是基于红黑树封装的数据结构,因此具有增删高效,查找高效的特点。set在插入时就是有序插入,并且不允许重复。于是我们在横向、纵向、主对角线方向和副对角线方向维护:

#include<vector>
#include<set>
vector<set<location>*> location_set[4]

        以题例为例,构建出的 location_set是这样的:


        小贴士:

        由于set集合insert时需要比较元素之间的大小来在合适的位置插入,因此我们要重载location结构的 “<” 运算符,为了之后计算方便我顺便重载了 “-” 法运算 :

const bool operator < (const location l1, const location l2) {
    if (l1.x != l2.x)
        return l1.x < l2.x;
    else
        return l1.y < l2.y;
}

const long long operator - (const location l1, const location l2) {
    return abs(l1.x == l2.x) ? abs(l1.y - l2.y) : abs(l1.x - l2.x);
}

        在查找一个set中第一个大于和小于某元素的元素时,upper_bound 和 lower_bound 就是使用的二分查找法,具体使用过程:

void get_find_list(location l, long long value, int flag, long long& k) {

    if (setID[flag][value] == 0) return;  //若不存在这个集合,跳过搜寻

    set<location>::iterator upperit = location_set[flag][setID[flag][value] - 1]->upper_bound(l);
    set<location>::iterator lowerit = location_set[flag][setID[flag][value] - 1]->lower_bound(l);
    long long temp;

    if (lowerit != location_set[flag][setID[flag][value] - 1]->begin()) {
        lowerit--;
        find_list[flag * 2 + 1] = lowerit->pID;
        temp = l - *lowerit;
        k = temp < k ? temp : k;
    }

    if (upperit != location_set[flag][setID[flag][value] - 1]->end()) {
        find_list[flag * 2] = upperit->pID;
        temp = l - *upperit;
        k = temp < k ? temp : k;
    }
}

        lower_bound(l) 返回set中第一个>= l 的元素指针,减一就得到第一个<l 的元素指针;

        upper_bound(l) 返回set中第一个> l 的元素指针

        这样,计算阴阳龙8个方向的人员的最短距离 (d1)就可以高效实现了。

最终代码 (100分)

#include<iostream>
#include<vector>
#include<math.h>
#include<set>
#include<map>

using namespace std;

int op[8][2] = { {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, {0,-1}, {1,-1} };
map<int, int> op_map{ {0, 0}, {1, 4}, {2, 2}, {3, 6}, {4, 1}, {5, 5}, {6, 7}, {7, 3} };

struct location {
    long long x;
    long long y;
    int pID;

    location(long long x = 0, long long y = 0, int pID = 0) {
        this->pID = pID;
        this->x = x;
        this->y = y;
    }

};

const bool operator < (const location l1, const location l2) {
    if (l1.x != l2.x)
        return l1.x < l2.x;
    else
        return l1.y < l2.y;
}

const long long operator - (const location l1, const location l2) {
    return abs(l1.x == l2.x) ? abs(l1.y - l2.y) : abs(l1.x - l2.x);
}

location p_list[100010];                           // 存放所有人员location  
vector<set<location>*> location_set[4];            //存放横向:0、竖向:1、主对角线:2、副对角线:3三个方向的位置 
map<long long, long long> setID[4];                //存放四个方向的 <集合号, 实际存放集合号+1> 
vector<long long> find_list(8);                    //待进行位置变换的人员 0:x+, 1:x-, 2:y+, 3:y-, 4:(x+y)+, 5(x+y)-, 6(x-y)+, 7(x-y)-

void insert_location(location l, long long value, int flag) {
    if (setID[flag][value] == 0) {    //表示该集合还没有元素 
        location_set[flag].push_back(new set<location>());
        setID[flag][value] = location_set[flag].size();
    }
    location_set[flag][setID[flag][value] - 1]->insert(l);
}

void delete_location(location l, long long value, int flag) {
    location_set[flag][setID[flag][value] - 1]->erase(l);
}

void transform(location yyl, long long t, long long k) {
    for (int i = 0; i < 8; i++) {
        if (find_list[i] == 0)
            continue;

        if (p_list[find_list[i]] - yyl != k)
            continue;

        // 更新四个set
        delete_location(p_list[find_list[i]], p_list[find_list[i]].y, 0);
        delete_location(p_list[find_list[i]], p_list[find_list[i]].x, 1);
        delete_location(p_list[find_list[i]], p_list[find_list[i]].x - p_list[find_list[i]].y, 2);
        delete_location(p_list[find_list[i]], p_list[find_list[i]].x + p_list[find_list[i]].y, 3);

        int opindex = op_map[i];
        p_list[find_list[i]].x = yyl.x + k * op[(opindex + t) % 8][0];
        //cout << find_list[i] << "  :" << yyl.x << "+" << k << "*" << op[(opindex +t)%8][0] << "=" << p_list[find_list[i]].x << "  ";

        p_list[find_list[i]].y = yyl.y + k * op[(opindex + t) % 8][1];
        //cout << yyl.y << "+" << k << "*" << op[(opindex +t)%8][1] << "=" << p_list[find_list[i]].y << endl;
    }

    for (int i = 0; i < 8; i++) {
        if (find_list[i] == 0)
            continue;

        if (p_list[find_list[i]] - yyl != k)
            continue;

        // 更新四个set
        insert_location(p_list[find_list[i]], p_list[find_list[i]].y, 0);
        insert_location(p_list[find_list[i]], p_list[find_list[i]].x, 1);
        insert_location(p_list[find_list[i]], p_list[find_list[i]].x - p_list[find_list[i]].y, 2);
        insert_location(p_list[find_list[i]], p_list[find_list[i]].x + p_list[find_list[i]].y, 3);
    }
}

void get_find_list(location l, long long value, int flag, long long& k) {

    if (setID[flag][value] == 0) return;

    set<location>::iterator upperit = location_set[flag][setID[flag][value] - 1]->upper_bound(l);
    set<location>::iterator lowerit = location_set[flag][setID[flag][value] - 1]->lower_bound(l);
    long long temp;

    if (lowerit != location_set[flag][setID[flag][value] - 1]->begin()) {
        lowerit--;
        find_list[flag * 2 + 1] = lowerit->pID;
        temp = l - *lowerit;
        k = temp < k ? temp : k;
    }

    if (upperit != location_set[flag][setID[flag][value] - 1]->end()) {
        find_list[flag * 2] = upperit->pID;
        temp = l - *upperit;
        k = temp < k ? temp : k;
    }
}

int main() {
    long long n, m, p, q, xi, yi;
    long long ui, vi, ti;             //阴阳龙现身的位置和强度
    location yinyanglong;             // 阴阳龙的location 
    vector<vector<location>> k_list;  // 存放距阴阳龙距离为k的人员的location

    cin >> n >> m >> p >> q;
    // 初始化所有人员 
    for (int i = 1; i <= p; i++) {
        cin >> xi >> yi;
        p_list[i] = location(xi, yi, i);
        // 每个人都同时属于四个集合
        insert_location(p_list[i], yi, 0);
        insert_location(p_list[i], xi, 1);
        insert_location(p_list[i], xi - yi, 2);
        insert_location(p_list[i], xi + yi, 3);
    }
    // 阴阳龙开始刷新
    for (int i = 0; i < q; i++) {

        cin >> ui >> vi >> ti;
        yinyanglong = location(ui, vi);
        find_list = { 0,0,0,0,0,0,0,0 };

        // 计算k
        long long k = 1e10;

        //朝阴阳龙所在的8个方向寻找 
        get_find_list(yinyanglong, yinyanglong.y, 0, k);
        get_find_list(yinyanglong, yinyanglong.x, 1, k);
        get_find_list(yinyanglong, yinyanglong.x - yinyanglong.y, 2, k);
        get_find_list(yinyanglong, yinyanglong.x + yinyanglong.y, 3, k);

        //计算阴阳龙到边界的最短距离 
        long long min_yyl_x, min_yyl_y, min_yyl;
        min_yyl_x = yinyanglong.x - 1 <= n - yinyanglong.x ? yinyanglong.x - 1 : n - yinyanglong.x;
        min_yyl_y = yinyanglong.y - 1 <= m - yinyanglong.y ? yinyanglong.y - 1 : m - yinyanglong.y;
        min_yyl = min_yyl_x < min_yyl_y ? min_yyl_x : min_yyl_y;

        k = min_yyl < k ? 0 : k;

        //对 min_list 中的人员进行位置变换
        transform(yinyanglong, ti, k);

    }

    // 计算最终结果
    long long result = p_list[1].x + p_list[1].y;
    for (long long i = 2; i <= p; i++) {
        result ^= (i * p_list[i].x + p_list[i].y);
    }
    cout << result;
}

过程中出现的一些问题

        set的更新部分,insert 和 delete 不能在同一循环中,需要分开写,否则 delete 没有结束就 insert 可能会把已存在的点覆盖。因为set中元素是互斥的。

        find_list 在每次阴阳龙刷新后要重置为0,小问题dubug好久才发现........

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值