题目回顾:
阴阳龙https://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;
}
于是,我朴素的进行如下操作求解,这样一套操作完成,时间复杂度为:
#伪代码 |
1. 输入,初始化地图和人员 |
2. 阴阳龙开始刷新 |
3. 计算阴阳龙刷新位置到所有人员的最短距离 (d1) |
4. 计算阴阳龙到边界的最短距离 (d2) |
5. k = d1<d2 ? d1:0 |
6. 更新人员 |
7. 计算最终结果 |
end |
提交之后发现只通过了用例2,剩下的都是运行超时(用例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好久才发现........