挑战一
题目描述:
小蓝鲸开始冒险,但是异世界的征途会遇到各种妖魔鬼怪,小蓝鲸如果想要走得更远,必须提高自己的战力值。小蓝鲸为了能够存活下来并且继续冒险,来到了小怪的聚集地,决定通过打小怪的方式来积攒经验并且提升战力值。
每当小蓝鲸击败一个小怪时,小蓝鲸的战力值都会发生改变。具体而言,每个小怪也同样具有一个战力值,假设已经有
x
个小怪被小蓝鲸战胜,那么小蓝鲸的战力值就会变为这些小怪中战力最高的⌈x/M⌉
个小怪中战力最低的小怪的战力值。小蓝鲸需要知道每当他战胜一个小怪时,自己的战力值是多少
输入
2 4
1 2 4 3
输出
1 2 2 3
解释 当数组为[1],第⌈1/2⌉
大的数就是1,当数组为[1,2],第 ⌈2/2⌉
大的数就是2,当数组为[1,2,4],第⌈3/2⌉
大的数就是2,当数组为[1,2,4,3],第⌈4/2⌉
大的数就是3
分析
第一个暴力是比较直观的:for循环表示战胜了i个小怪,然后每战胜一个,就重新对已经战胜的小怪战力值数组排序,然后直接取
⌈x/M⌉
相应的值。排序为了加快可以用Logn的快排或者归并排序,这样很明显时间复杂度为O(nlogn)。
代码如下:
void swap(int *nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
void merge_sort(int* nums, int l, int r, int* temp) {
if (l + 1 >= r) {
return;
}
// divide
int m = l + (r - l) / 2;
merge_sort(nums, l, m, temp);
merge_sort(nums, m, r, temp);
// conquer
int p = l, q = m, i = l;
while (p < m || q < r) {
if (q >= r || (p < m && nums[p] <= nums[q])) {
temp[i++] = nums[p++];
}
else {
temp[i++] = nums[q++];
}
}
for (i = l; i < r; ++i) {
nums[i] = temp[i];
}
}
int main() {
int m, n;
cin >> m >> n;
int* nums = new int[n];
int* buff = new int[100000];
int* temp = new int[100000];
for (int i = 0; i < n; ++i) {
cin >> nums[i];
}
for (int i = 0; i < n; ++i) {
buff[i] = nums[i];
merge_sort(buff, 0, i+1,temp);
int x = ceil(((double)i + 1) / (double)m);
cout << buff[i - (x - 1)] << " ";
}
return 0;
}
那这样根据题目的数据和限制范围,可能有几个是过不了的,事实上我8/10。
所以就得优化方法了。
这里提供一种思路:
使用两个堆,一个最大堆,一个最小堆。
其中,最小堆存的前[X/M]大的元素,那样我们每次输出就取最小堆的头即可,最大存剩下的元素。然后随着元素的插入(X的变大),我们需要对两个堆进行一些调整。
这样,取答案的时间就是O(1),每次调整的时间复杂度也控制在O(logn),大大加快了速度,不过牺牲了一些空间。
template <class T>
class MaxHeap {
private:
int capacity;
int curSize;
T* data;
public:
MaxHeap(int eleNums) : capacity(eleNums), curSize(0) // 构造函数
{
data = new T[eleNums + 4];
data[0] = INT_MAX; // 索引为0的空间不存放有效数据,只作为辅助用,这个很重要
}
T GetMax() {
if (curSize == 0)return -1;
return data[1];
}
// 插入,使用空穴上浮
void Insert(int x)
{
int i = curSize + 1; // 空穴的初始位置
while (data[i / 2] < x) // 只要空穴值大于空穴的父节点值,空穴就要上浮
{ // 索引为 0 的数的作用在这体现,使循环条件更简单
data[i] = data[i / 2]; // 空穴的父节点下沉
i /= 2; // 空穴上浮
}
data[i] = x; // 给空穴赋值
curSize++; // 堆中有效数据加一
}
//删除,使用空穴下沉
void RemoveMax(int& curMax)
{
if (curSize != 0) // 删除前先判断堆中是否还有有效数据
{
curMax = data[1]; // 获取堆中当前最大值
int i = 1; // 空穴的初始位置
int x = data[curSize--]; // 空穴值取最后一个元素的值
int child; // 表示空穴孩子的位置
while (i * 2 <= curSize) // 空穴最多下沉到叶子节点,注意,不是一定下沉到叶子节点
{
child = i * 2; // 先取空穴的左孩子位置
if (child != curSize && data[child] < data[child + 1]) // 找出空穴的较大的孩子
child++; // 空穴存在右孩子,且右孩子比左孩子大
if (x < data[child]) // 空穴值小于空穴的孩子值,那么空穴需要下沉,否则跳出循环
{
data[i] = data[child]; // 空穴的较大的孩子上浮
i = child; // 空穴下沉
}
else
break;
}
data[i] = x; // 将空穴值放入空穴位置
}
}
};
template <class T>
class MinHeap
{
public:
MinHeap(int sz); //构造函数,建立空堆
MinHeap(T arr[], int n); //构造函数,通过一个数组建立堆,这个数组有n个元素
~MinHeap() { delete[] heap; } //析构函数
bool Insert(const T& x); //将新元素的关键码x插入到最小堆中
bool Remove(T& x); //删除堆顶的最小元素
int GetMin();
bool IsEmptmatric() const { return (currentSize == 0) ? true : false; }
bool IsFull() const { (currentSize == maxHeapSize) ? true : false; }
void MakeEmptmatric() { currentSize = 0; } //内存并没有释放
template<class R>
friend ostream& operator<<(ostream& out, MinHeap<R>& hp);
private:
T* heap; //存放最小堆中元素关键码的数组
int currentSize; //最小堆中当前元素的个数
int maxHeapSize; //最小堆最多允许的元素个数
void siftDown(int start, int m); //从start到m下滑调整成为最小堆
void siftUp(int start); //从start到0上滑调整成为最小堆
};
template<class T>
MinHeap<T>::MinHeap(int sz) //参数再写int sz = DefaultSize报错
{
maxHeapSize = sz;//实际中要对sz检查一下
heap = new T[maxHeapSize]; //创建堆存储空间(就是简单的数组)
if (heap == nullptr)
{
cerr << "内存分配失败" << endl;
exit(1);
}
currentSize = 0;
}
template<class T>
MinHeap<T>::MinHeap(T arr[], int n)
{
//maxHeapSize = (DefaultSize < n) ? n : DefaultSize; //若n>DefaultSize,则堆是满的;否则堆不满(默认数组没有空的内存,存满了数据)
maxHeapSize = n;
heap = new T[maxHeapSize];
if (heap == nullptr)
{
cerr << "内存分配失败!" << endl;
exit(1);
}
//把数组arr复制到堆数组中
for (int i = 0; i < n; i++)
{
heap[i] = arr[i];
}
currentSize = n;
int currentPos = (currentSize - 1) / 2; //找到最初调整的位置:最后拥有子女结点的结点即最后的那个父节点
while (currentPos >= 0) //控制多次局部最小堆以实现全局都是最小堆
{
siftDown(currentPos, currentSize - 1); //局部自上向下下滑调整。各个元素关键码的下表是从0开始算的,所以最后一个关键码的位置是currentSize - 1
currentPos--;
}
}
//最小堆的下滑调整算法(局部调整)
//虽然siftDown是私有权限,但是该权限是对对象而言的,类外实现的时候只需加作用域即可写代码实现该函数
template<class T>
void MinHeap<T>::siftDown(int start, int m)
{
//从start结点开始到m节点位置,自上向下比较,如果子女的关键码小于父节点的关键码,则关键码小的上浮,继续往
//下层比较,这样将以start为根节点的子树调整为局部的最小堆
int i = start, j = 2 * i + 1; //j是作左子女的位置
while (j <= m) //访问不超出m节点位置
{
if (j < m && heap[j + 1] < heap[j])
j++; //让j指向两子女的小者
if (heap[j] > heap[i]) break; //如果子女的关键码比父节点的大,则不做调整
else //关键码交换
{
T temp = heap[i];
heap[i] = heap[j]; //小的关键码上浮
i = j; //更新根节点下表
j = 2 * j + 1; //新根节点的左子女下表
heap[i] = temp; //大的关键码下沉到刚刚的子女位置
}
}
}
//最小堆的上滑调整算法,调整的是全局
template<class T>
void MinHeap<T>::siftUp(int start)
{
//从start开始到结点0为止,自下向上比较,如果子女的关键码小于父节点的关键码,则相互交换。这样将集合重新调整为最小堆
int j = start, i = (j - 1) / 2; //和siftDown一样,j指向子女,i指向父节点
while (j > 0)
{
if (heap[i] < heap[j]) break; //如果父节点更小,则不用做调整
else //关键码交换
{
T temp = heap[j]; //记录子女关键码
heap[j] = heap[i]; //父节点关键码下移
j = i; //当前父节点作为上一层调整的子女结点
i = (j - 1) / 2; //新的父节点
heap[j] = temp; //小的关键码上浮
}
}
}
//插入元素的关键码总会放在已存在最小堆的最后面,然后将其与父节点的关键码进行比较,完成对调以形成新的最小堆
//关键码比较回调的过程用到了堆的上滑调整操作siftUp
template<class T>
bool MinHeap<T>::Insert(const T& x)
{
//将新元素的关键码x插入到最小堆中
if (currentSize == maxHeapSize)
{
cerr << "Heap Full!" << endl;
return false;
}
heap[currentSize] = x; //先将关键码放在堆的最后
siftUp(currentSize); //从最后的位置开始回调
currentSize++;
return true;
}
//删除堆顶的最小元素
template<class T>
bool MinHeap<T>::Remove(T& x)
{
//删除堆顶的最小关键码后,一般以堆的最后一个元素填补该位置,并将currentSize减一。此时最小堆已被破坏,所以调用siftDown再次调整
if (!currentSize) //如果堆为空
{
cerr << "Heap Emptmatric!" << endl;
return false;
}
x = heap[0]; //取堆顶最小关键码
heap[0] = heap[currentSize - 1]; //最后的关键码补顶
heap[currentSize - 1] = x; //堆顶的元素放在数组的后面,虽然下一句currentSize--后不能再输出该值,但是可以为堆排序做铺垫
currentSize--;
siftDown(0, currentSize - 1); //从堆顶向下调整为堆
return true;
}
template<class T>
int MinHeap<T>::GetMin()
{
return heap[0];
}
//堆的存储形式为完全二叉树,按照完全二叉树从上往下,从左往右的顺序依次输出个元素的关键码
template<class R>
ostream& operator<<(ostream& out, MinHeap<R>& hp)
{
if (hp.currentSize == 0)
{
out << "Heap Emptmatric!" << endl;
}
else
{
for (int i = 0; i < hp.currentSize; i++)
out << hp.heap[i] << " ";
}
out << endl;
return out;
}
int main() {
int m, n;
cin >> m >> n;
int* nums = new int[n+1];
for (int i = 0; i < n; ++i) {
scanf("%d",&nums[i]);
}
MinHeap<int> minheap(500000);//里面存X/M个最大的元素
MaxHeap<int> maxheap(500000);//里面存X-X/M个剩下的元素
int min_len=0;
for (int i = 0; i < n; ++i) {
int max1 = maxheap.GetMax();
int max2 = minheap.GetMin();
int k = ceil(((double)i + 1) / (double)m);
if (k > min_len) {//就是最小堆要加元素了
if (max1 <= nums[i]) {
minheap.Insert(nums[i]);
}
else if (max1 > nums[i]) {
//那就拿出大顶堆元素加入小顶堆里面,再把nums[i]加入大顶堆
int temp;
maxheap.RemoveMax(temp);
minheap.Insert(temp);
maxheap.Insert(nums[i]);
}
++min_len;
}
else if (k == min_len) {
//也就是X/M不变,小顶堆的数量不断,都是内部元素可能要调整
if (nums[i] <= max2) {
//说明最小堆不需要变
maxheap.Insert(nums[i]);
}
else {
int temp;
minheap.Remove(temp);
minheap.Insert(nums[i]);
maxheap.Insert(temp);
}
}
//最后取最小堆的头
cout << minheap.GetMin() << " ";
}
return 0;
}
挑战二
题目描述
思路
首先提到最短路径。然后又给了边的权值,且都大于0,这样很容易想到要用迪杰斯特拉算法。然后,通过自己画几次图,走一下三四种情况,我们发现一个特点,也即,两个勇者总是要有一个汇合点的,汇合点可以是终点,也可以半路汇合。
于是很自然地,我们想到对两个src分别用一次迪杰斯特拉算法,保存在两个dis数组里面(我习惯性用num命名),然后最终消耗 = 汇合点到终点的消耗 + 两个勇者分别到汇合点的消耗。
那么只剩下汇合点到终点的消耗还没求出来了,怎么求呢?我一开始是正向思维,顺着想再用一次迪杰斯特拉,但是一细想就发现,如果我遍历的时候每次都要迪杰斯特拉,那需要n次迪杰斯特拉,这肯定会超时.......所以需要反向思考一下,再进行一次迪杰斯特拉就够了,从终点反向走到汇合点。
然后需要提三个注意点:
1.朴素迪杰斯特拉太慢了,需要用优先队列优化,那如果不能用优先队列,难点在于实现它,其实我们可以用最小二叉堆来实现优先队列。
2.如果路径用邻接表存,那么在优化后的迪杰斯特拉算法内,会出现复杂度退化的情况,也即:
我们发现这里为了找到从这一点到其他点的边,我们必须要进行On的遍历,这就导致复杂度退化了,因此我们需要使用出边表来存储。
3.如果用出边表来存,那么反向的路径就比较难搞,所以还需要存一个入边表用于反向迪杰斯特拉。
实现如下:
struct Path {
int from;
int to;
int cost;
bool operator > (Path const& W) const {
return cost > W.cost;
}
bool operator < (Path const& W) const {
return cost < W.cost;
}
};
template<typename T>//小顶堆
class minHeap {
public:
int g_size;
T* arr;
minHeap(int n) {
g_size = 0;
arr = (T*)malloc(sizeof(T) * n);
}
bool empty() {
return g_size == 0;
}
bool cmp(T a, T b)
{
return a > b; //最小堆
}
void exchange(T* a, T* b)
{
T tmp = *a;
*a = *b;
*b = tmp;
}
int LeftChild(int id)
{
return id * 2;
}
int RightChild(int id)
{
return id * 2 + 1;
}
int Parent(int id)
{
return id / 2;
}
void AdjustHeap(int rootId, int size)
{
int largest = rootId, left = LeftChild(rootId), right = RightChild(rootId);
if (left < size && cmp(arr[largest], arr[left]))largest = left;
if (right < size && cmp(arr[largest], arr[right]))largest = right;
if (largest == rootId)return;
exchange(arr + rootId, arr + largest);
AdjustHeap(largest, size);
}
void HeapIncrese(int size, int id, T newValue)
{
arr[id] = newValue;
while (id > 0 && cmp(arr[Parent(id)], arr[id])) {
exchange(arr + id, arr + Parent(id));
id = Parent(id);
}
}
void HeapInsert(int& size, T value)
{
HeapIncrese(size + 1, size, value);
size++;
}
T Top()
{
return arr[0];
}
void Push(T value)
{
HeapInsert(g_size, value);
}
void Pop()
{
g_size--;
exchange(arr, arr + g_size);
AdjustHeap(0, g_size);
}
};
struct Node
{
int index;
int dis;
bool operator > (Node const& W) const {
return dis > W.dis;
}
bool operator < (Node const& W) const {
return dis < W.dis;
}
};
struct List
{
List* next;
int to;
int cost;
};
void insert(List* L, int to, int cost) {
while (L->next != NULL) {
L = L->next;
}
L->to = to;
L->cost = cost;
L->next = (List*)malloc(sizeof(List));
L->next->next = NULL;
L->next->to = -1;
}
int main() {
//城市编号0-m-1
int m, n, src1, src2, dest;
cin >> m >> n >> src1 >> src2 >> dest;
int* nums1 = new int[m];//nums1[i]表示从src1到编号为i的城市所用最小路径,无法到达则填MAX;
memset(nums1, 1000000, sizeof(int) * m);
int* nums2 = new int[m];//nums2[i]表示从src2到编号为i的城市所用最小路径,无法到达则填-1;
memset(nums2, 1000000, sizeof(int) * m);
int* nums3 = new int[m];//nums3[i]表示从dest到编号为i的城市所用最小路径,无法到达则填-1;
memset(nums3, 1000000, sizeof(int) * m);
List* path1 = (List*)malloc(sizeof(List) * m);//出边的链表
List* path2 = (List*)malloc(sizeof(List) * m);//入边的链表
for (int i = 0; i < m; ++i) {
path1[i].next = NULL;
path1[i].to = -1;
path2[i].next = NULL;
path2[i].to = -1;
}
minHeap<Node> mh1(m);
minHeap<Node> mh2(m);
minHeap<Node> mh3(m);
for (int i = 0; i < n; ++i) {
int a, b, c;
cin >> a >> b >> c;
insert(&path1[a], b, c);
insert(&path2[b], a, c);
}
mh1.Push({ src1,0 });
nums1[src1] = 0;
int* visited = new int[m];
memset(visited, 0, sizeof(int) * m);
while (!mh1.empty()) {
Node temp = mh1.Top();
mh1.Pop();
if (visited[temp.index])continue;
visited[temp.index] = true;
List* p = &path1[temp.index];
while (p->to != -1) {
if (nums1[p->to] > p->cost + nums1[temp.index]){
nums1[p->to] = p->cost + nums1[temp.index];
mh1.Push({ p->to ,nums1[p->to] });
}
p = p->next;
}
}
nums2[src2] = 0;
memset(visited, 0, sizeof(int) * m);
mh2.Push({ src2,0 });
nums2[src2] = 0;
while (!mh2.empty()) {
Node temp = mh2.Top();
mh2.Pop();
if (visited[temp.index])continue;
visited[temp.index] = true;
List* p = &path1[temp.index];
while (p->to != -1) {
if (nums2[p->to] > p->cost + nums2[temp.index]) {
nums2[p->to] = p->cost + nums2[temp.index];
mh2.Push({ p->to ,nums2[p->to] });
}
p = p->next;
}
}
nums3[dest] = 0;
memset(visited, 0, sizeof(int) * m);
mh3.Push({ dest,0 });
nums3[dest] = 0;
while (!mh3.empty()) {
Node temp = mh3.Top();
mh3.Pop();
if (visited[temp.index])continue;
visited[temp.index] = true;
List* p = &path2[temp.index];
while (p->to != -1) {
if (nums3[p->to] > p->cost + nums3[temp.index]) {
nums3[p->to] = p->cost + nums3[temp.index];
mh3.Push({ p->to ,nums3[p->to] });
}
p = p->next;
}
}
int res = INT_MAX - 1;
for (int i = 0; i < m; ++i) {
if (nums1[i] < 1000000 && nums2[i] < 1000000 && nums3[i] < 1000000) {
int temp = nums1[i] + nums2[i] + nums3[i];
if (temp < res) {
res = temp;
}
}
}
res = res == INT_MAX - 1 ? -1 : res;
cout << res;
return 0;
挑战三
题目描述:
思路:
其实这里更多是一个数据结构题,不是算法题了,我用了3种办法:
方法一:
很自然地用链表存储冒险家信息,并且以id为关键值,维持顺序链表,然后查询就On,加一些剪枝,同时使用printf\scanf加快IO速度,最后我过了8/10,有两个tle.
方法二:
知道了链表查询速度不行,那就从存储结构优化,我先选了简单一点的二叉搜索树,但是结果不太乐观,有6个tle,因此想到了二叉搜索树退化成单链树的情况,并且也和助教确认过了,所以要换结构。
方法三:
也即是最终答案,使用AVL平衡树存储冒险家信息,成功解决问题,当然AVL不是很熟悉,还是写了老一会的。
代码:AVL树版本:
struct People
{
string name = "";
People* left;
People* right;
int strength;
int id;
int h;
People(const int _id, const int _s, const string _n)
:id(_id)
, left(NULL)
, right(NULL)
, strength(_s)
,name(_n)
,h(1)
{}
};
class BSTree {
public:
People* root;
int size;
BSTree() {
root = NULL;
size = 0;
}
BSTree(const BSTree & t)
{
root = _Copy(t.root);
}
People* _Copy(People* root)
{
if (root == NULL)
{
return NULL;
}
People* copyPeople = new People(root->id,root->strength,root->name);
copyPeople->left = _Copy(root->left);
copyPeople->right = _Copy(root->right);
return copyPeople;
}
BSTree& operator=(BSTree t)
{
swap(root, t.root);
return *this;
}
People* add(People* cur, const int& id, const int& str, const string& na) {
if (cur == NULL) {//空树直接new出节点并返回
return new People(id,str,na);
}
else {
if (cur->id < id) {//比当前节点大
cur->right = add(cur->right, id, str, na);//去cur的右边插入并将新的头部返回
}
else {
cur->left = add(cur->left, id, str, na);//去cur的左边插入并将新的头部返回
}
cur->h = max(cur->left != NULL ? cur->left->h : 0, cur->right != NULL ? cur->right->h : 0) + 1;//重新计算高度
return maintain(cur);//对cur这个树进行调整
}
}
bool Insert(const int& id, const int& str, const string& na) {
/*People* lastPeople = findLastIndex(id);
if (lastPeople && lastPeople->id == id) {
return false;
}
else {*/
size++;
root = add(root, id,str,na);
return true;
}
//找到最离id最近的节点
People* findLastIndex(const int id) {
People* pre = root;//记录前一个节点
People* cur = root;
while (cur) {
pre = cur;
if (cur->id == id) {
break;
}
else if (cur->id > id) {
cur = cur->left;
}
else {
cur = cur->right;
}
}
return pre;
}
People* rightRotate(People* cur) {
People* left = cur->left;
cur->left = left->right;
left->right = cur;
cur->h = max((cur->left != NULL ? cur->left->h : 0), (cur->right != NULL ? cur->right->h : 0)) + 1;//更新高度
left->h = max((left->left != NULL ? left->left->h : 0), (left->right != NULL ? left->right->h : 0)) + 1;//更新高度
return left;//返回新的头
}
People* leftRotate(People* cur) {
People* rightPeople = cur->right;
cur->right = rightPeople->left;
rightPeople->left = cur;
cur->h = max((cur->left != NULL ? cur->left->h : 0), (cur->right != NULL ? cur->right->h : 0)) + 1;//更新高度
rightPeople->h = max((rightPeople->left != NULL ? rightPeople->left->h : 0), (rightPeople->right != NULL ? rightPeople->right->h : 0)) + 1;//更新高度
return rightPeople;//
}
People* maintain(People* cur) {
if (cur == NULL) {
return NULL;
}
int leftHeight = cur->left != NULL ? cur->left->h : 0;//计算出cur左树的高度
int rightHeight = cur->right != NULL ? cur->right->h : 0;//计算出cur右树的高度
if (abs(leftHeight - rightHeight) > 1) {//出现不平衡
if (leftHeight > rightHeight) {//如果是左树高
//把左树的左右子树的高度来出来比较看到底是左边高还是右边高
int leftLeftHeight = cur->left != NULL && cur->left->left != NULL ? cur->left->left->h : 0;
int leftRightHeight = cur->left != NULL && cur->left->right != NULL ? cur->left->right->h : 0;
if (leftLeftHeight >= leftRightHeight) {//注意想等时只能右旋
cur = rightRotate(cur);
}
else {//左右双旋
cur->left = leftRotate(cur->left);
cur = rightRotate(cur);
}
}
else {
int rightLeftHeight = cur->right != NULL && cur->right->left != NULL ? cur->right->left->h : 0;
int rightRightHeight = cur->right != NULL && cur->right->right != NULL ? cur->right->right->h : 0;
if (rightRightHeight >= rightLeftHeight) {
cur = leftRotate(cur);
}
else {//右左双旋
cur->right = rightRotate(cur->right);
cur = leftRotate(cur);
}
}
}
return cur;//返回调整好的新头
}
People* FindID(const int id)
{
People* cur = root;
while (cur)
{
if (cur->id < id)
{
cur = cur->right;
}
else if (cur->id > id)
{
cur = cur->left;
}
else
{
printf("%d ", cur->id);
cout <<cur->name;
printf(" %d\n",cur->strength);
return cur;//题目保证有解了
}
}
}
void FindName(People* r,const string& s)//T.findname(T.root)
{
People* root1 = r;
if (root1)
{
FindName(root1->left,s);
if (root1->name==s) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindName(root1->right,s);
}
}
//>,<。。。
void FindCompareTwo(People* p, const int& flag, const int& value,const int& id1, const int& id2) {
if (flag == 0) {
//>
People* root1 = p;
if (root1)
{
FindCompareTwo(root1->left, flag,value,id1,id2);
if (root1->id > id2) {
return;
}
if (root1->id >= id1 && root1->strength > value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareTwo(root1->right, flag, value, id1, id2);
}
}
else if (flag == 1) {
//<
People* root1 = p;
if (root1)
{
FindCompareTwo(root1->left, flag, value, id1, id2);
if (root1->id > id2) {
return;
}
if (root1->id >= id1 && root1->strength < value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareTwo(root1->right, flag, value, id1, id2);
}
}
else if (flag == 2) {
//>=
People* root1 = p;
if (root1)
{
FindCompareTwo(root1->left, flag, value, id1, id2);
if (root1->id > id2) {
return;
}
if (root1->id >= id1 && root1->strength >= value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareTwo(root1->right, flag, value, id1, id2);
}
}
else if (flag == 3) {
//<=
People* root1 = p;
if (root1)
{
FindCompareTwo(root1->left, flag, value, id1, id2);
if (root1->id > id2) {
return;
}
if (root1->id >= id1 && root1->strength <= value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareTwo(root1->right, flag, value, id1, id2);
}
}
else if (flag == 4) {
//!=
People* root1 = p;
if (root1)
{
FindCompareTwo(root1->left, flag, value, id1, id2);
if (root1->id > id2) {
return;
}
if (root1->id >= id1 && root1->strength != value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareTwo(root1->right, flag, value, id1, id2);
}
}
else if (flag == 5) {
People* root1 = p;
if (root1)
{
FindCompareTwo(root1->left, flag, value, id1, id2);
if (root1->id > id2) {
return;
}
if (root1->id >= id1 && root1->strength == value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareTwo(root1->right, flag, value, id1, id2);
}
}
}
void FindCompareOne(People* p, const int& flag, const int& value) {
if (flag == 0) {
//>
People* root1 = p;
if (root1)
{
FindCompareOne(root1->left, flag, value);
if (root1->strength > value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareOne(root1->right, flag, value);
}
}
else if (flag == 1) {
//<
People* root1 = p;
if (root1)
{
FindCompareOne(root1->left, flag, value);
if (root1->strength < value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareOne(root1->right, flag, value);
}
}
else if (flag == 2) {
//>=
People* root1 = p;
if (root1)
{
FindCompareOne(root1->left, flag, value);
if (root1->strength >= value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareOne(root1->right, flag, value);
}
}
else if (flag == 3) {
//<=
People* root1 = p;
if (root1)
{
FindCompareOne(root1->left, flag, value);
if (root1->strength <= value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareOne(root1->right, flag, value);
}
}
else if (flag == 4) {
//!=
People* root1 = p;
if (root1)
{
FindCompareOne(root1->left, flag, value);
if (root1->strength != value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareOne(root1->right, flag, value);
}
}
else if (flag == 5) {
People* root1 = p;
if (root1)
{
FindCompareOne(root1->left, flag, value);
if (root1->strength == value) {
printf("%d ", root1->id);
cout << root1->name;
printf(" %d\n", root1->strength);
}
FindCompareOne(root1->right, flag, value);
}
}
}
void Erase(int id) {
if (root == NULL) {
return;
}
if (containsKey(id)) {
--size;
root = Delete(root, id);
}
}
bool containsKey(int key) {
if (root == NULL) {
return false;
}
People* lastNode = findLastIndex(key);
return lastNode && key == lastNode->id ? true : false;
}
People* Delete(People* cur, int id) {
if (cur->id < id) {
cur->right = Delete(cur->right, id);//去我的右边删除并且返回新的头部
}
else if (cur->id > id) {
cur->left = Delete(cur->left, id);//去我的左边删除并返回新的头部
}
else {
if (cur->left == nullptr && cur->right == nullptr) {//叶子节点左右都为空
delete cur;
return nullptr;
}
else if (!cur->left && cur->right) {//左为空但右不为空
People* subR = cur->right;
delete cur;
cur = subR;
}
else if (cur->left && !cur->right) {//右为空但左不为空
People* subL = cur->left;
delete cur;
cur = subL;
}
else {//左右都不为空采用替换法删除既采用值的方式替代
People* des = cur->right;//找到右树最左节点或者找到左树的最右节点都可以
while (des->left)
{
des = des->left;
}
//记录对应的值
int t_id = des->id;
int t_s = des->strength;
string t_n = des->name;
cur->right = Delete(cur->right, des->id);//找到我先不删调用Delete 将des删掉并将树调整好
cur->id = t_id;
cur->strength = t_s;
cur->name = t_n;
}
}
if (cur) {//重新计算高度
cur->h = max(cur->left ? cur->left->h : 0, cur->right ? cur->right->h : 0) + 1;
}
return maintain(cur);//调整cur这颗树
}
};
//分为两块,第一块就是数组直接id查,第二块就是people[m-1]后边要跟链表了
void Query(BSTree& T, string instr) {//传进来的去掉了“QUERY ”
if (instr.find("name =") != string::npos) {
instr.erase(0, 7);
T.FindName(T.root, instr);
}
else if (instr.find("strength") != string::npos) {
if (instr[0] == 's' && instr[1] == 't') {
instr.erase(0, 9);
int f;
string temp = instr.substr(0, 2);
if (temp == "> ") {
f = 0;
instr.erase(0, 2);
}
else if (temp == "< ") {
f = 1;
instr.erase(0, 2);
}
else if (temp == ">=") {
f = 2;
instr.erase(0, 3);
}
else if (temp == "<=") {
f = 3;
instr.erase(0, 3);
}
else if (temp == "!=") {
f = 4;
instr.erase(0, 3);
}
else if (temp == "= ") {
f = 5;
instr.erase(0, 2);
}
T.FindCompareOne(T.root, f, stoi(instr));
}
else {
string temp_id1 = instr.substr(0, instr.find_first_of(" "));
instr.erase(0, instr.find_first_of(" ") + 1);
string temp_id2 = instr.substr(0, instr.find_first_of(" "));
instr.erase(0, instr.find_first_of(" ") + 1);
instr.erase(0, 9);
int f;
string temp = instr.substr(0, 2);
if (temp == "> ") {
f = 0;
instr.erase(0, 2);
}
else if (temp == "< ") {
f = 1;
instr.erase(0, 2);
}
else if (temp == ">=") {
f = 2;
instr.erase(0, 3);
}
else if (temp == "<=") {
f = 3;
instr.erase(0, 3);
}
else if (temp == "!=") {
f = 4;
instr.erase(0, 3);
}
else if (temp == "= ") {
f = 5;
instr.erase(0, 2);
}
T.FindCompareTwo(T.root, f, stoi(instr), stoi(temp_id1), stoi(temp_id2));
}
}
else {
T.FindID(stoi(instr));
}
}
int main() {
int n, m;
cin >> m >> n;
BSTree T;
string na = "";
int s;
int id;
for (int i = 0; i < m; ++i) {
scanf("%d", &id);
cin >> na;
scanf("%d", &s);
T.Insert(id, s, na);
}
getline(cin, na);
for (int i = 0; i < n; ++i) {
string instr;
getline(cin, instr);
if (instr.find("QUERY") != string::npos) {
instr.erase(0, 6);
Query(T, instr);
}
else if (instr.find("DELETE") != string::npos) {
instr.erase(0, 7);
if (instr.find(" ") == string::npos) {
T.Erase(stoi(instr));
}
else {
string temp = instr.substr(0, instr.find_first_of(" "));
instr.erase(0, instr.find_first_of(" ") + 1);
for (int i = stoi(temp); i <= stoi(instr); ++i) {
T.Erase(i);
}
}
}
else {
instr.erase(0, 7);
string temp_id = instr.substr(0, instr.find_first_of(" "));
instr.erase(0, instr.find_first_of(" ") + 1);
string temp_name = instr.substr(0, instr.find_first_of(" "));
instr.erase(0, instr.find_first_of(" ") + 1);
string temp_strength = instr.substr(0, instr.find_first_of(" "));
T.Insert(stoi(temp_id),stoi(temp_strength),temp_name);
}
}
return 0;
}
普通二叉搜索树版本这里就不展示了,其实就是Insert和erase两个操作发生了变化。
挑战四
思路
首先,第一个想法肯定是最简单的,单向dfs遍历7层,然后不断更新数量即可,但是这样很显然我们的时间复杂度会以指数级别上升,所以不是合理的选择。
而我们平时用于dfs加速的剪枝、记忆化搜索、visited数组加速,作用都十分有限,因此肯定要换方法,所以就提出了双向dfs。
双向dfs,顾名思义,就是正向搜索x层,反向搜索y层,只要x+y=7即可,而我根据了群里同学的提示,4+3和5+2比较快,所以我选择了4+3的做法。
那么首先讲反向dfs的思路,遍历到某一个点的时候,我们需要找到走两步可以到达该点的节点,距离该点小于3的节点,同时存储反向两层的路径,那存储边的时候入边表就需要了,标记距离该点小于3的节点,可以采取赋值head+1,这样能避免每次都对该数组memset重置,然后我们为了避免搜索到重复的环,每次必须保证搜索的环的起始节点id最小。
然后正向dfs的时候,首先需要出边表存储,然后在遍历的时候,进行判断,如果某个点是距离环起点小于3的节点,那就到存储反向两层路径的地方去遍历判断,如果没访问过的,那就可以构成答案的环,数量加一。
但是这一切都蕴含着一个问题,你怎么快速地找到某个节点id对应的某个储存单元呢?我们想最快,那就是直接下标访问,但是本题中id甚至可以达到21亿,那就开不了如此大的数组或者其他结构,因此我们想到要用哈希映射,那怎么解决冲突呢?一般有两种,第一个空间利用率高,然后实现简单,就是线性探测;第二个就是链式地址,查找速度更快一点点,但是实现起来略有麻烦。我这里选择了线性探测。
代码:
//正向走4步,反向走3步,难点在去重
//存的话我用出入边表存,但是这里需要hash了,并要处理冲突线性探测
struct List
{
long index;//数组里面的元素的index是编号,数组元素的链表里面的index是to
List* next;
bool vaild;//数组里面的元素要管这个,链表里面的不需要管
};
int answer = 0;
List* out;
List* in;
List* pathR; // 反向存储两层路径
int* rvis3; // 标记反向距离不超过3的所有结点
int* vis2; // 标记走两步可到达head的结点
bool* visited;//是否被访问过
//这些数组的哈希结果全部和in out保持一致,但是in out不一定一致
//在rdfs里面,都和in保持一致了,所以dfs里面也要和in保持一致
//为了避免搜索到重复的环,需要保证一个环的起始结点id最小
void rdfs(long head, long cur, int depth)
{
int hash = cur % 79999;
while (in[hash].index != cur && in[hash].vaild) {
++hash;
hash %= 79999;
}
visited[hash] = true;
List* r = &in[hash];
r = r->next;//第一个入边
while (r != NULL)
{
int v = r->index;
r = r->next;
if (v < head)continue;
int v_hash = v % 79999;
while (in[v_hash].index != v && in[v_hash].vaild) {
++v_hash;
v_hash %= 79999;
}
if ( visited[v_hash]) continue;
rvis3[v_hash] = head + 1;//这样可以避免每次都memset
if (depth == 2)
{
if (vis2[v_hash] != head + 1)//可能是初始值或者主函数里面上一轮里面求出来的head+1
{
pathR[v_hash].vaild = 0;
pathR[v_hash].next = NULL;
}
vis2[v_hash] = head + 1;
pathR[v_hash].vaild = 1;
pathR[v_hash].index = v;
List* r = &pathR[v_hash];
while (r->next != NULL) {
r = r->next;
}
r->next = (List*)malloc(sizeof(List));
r = r->next;
r->index = cur;
r->next = NULL;
r->vaild = 1;
}
if (depth < 3)
{
rdfs(head, v, depth + 1);
}
}
visited[hash] = false;
}
long* temp_path;
int tp_len;
void dfs(int head, int cur, int depth) {
int hash = cur % 79999;
while (out[hash].index != cur && out[hash].vaild) {
++hash;
hash %= 79999;
}
int inhash = cur % 79999;
while (in[inhash].index != cur && in[inhash].vaild) {
++inhash;
inhash %= 79999;
}
visited[inhash] = true;//visited反正每次都恢复的,rdfs用好就恢复的,这里跟着in走
//temp_path[tp_len++] = cur;
List* r = &out[hash];//这个则是out
r = r->next;//第一个出边
while (r != NULL) {
int v = r->index;
r = r->next;
if (v < head)continue;
int inv_hash = v % 79999;
while (in[inv_hash].index != v && in[inv_hash].vaild) {
++inv_hash;
inv_hash %= 79999;
}
if ( visited[inv_hash]) continue;//这里还是跟着in走,少算一个hash
//rvis3是跟着in走的
if (depth > 3 && rvis3[inv_hash] != head + 1) continue; // 交集才访问
// 环的判定条件: v结点走两步可到达head
//vis2也是跟着in走的
if (vis2[inv_hash] == head + 1) {
//temp_path[tp_len++] = v;// 该孩子结点放入临时数组
List* r = &pathR[inv_hash];
r = r->next;
while (r != NULL) {
int u = r->index;
r = r->next;
int u_hash = u % 79999;
while (in[u_hash].index != u && in[u_hash].vaild) {
++u_hash;
u_hash %= 79999;
}
if (visited[u_hash]) continue;//visited跟着in来
//temp_path[tp_len++] = u;
++answer; // 得到一条结果
/*for (int z = 0; z < tp_len; ++z)cout << temp_path[z] << " ";
cout << endl;*/
//--tp_len;
}
//--tp_len;
}
if (depth < 5) // 继续搜索
{
dfs(head, v, depth + 1);
}
}
visited[hash] = false;
//--tp_len;
}
int main() {
int n;
cin >> n;
temp_path = new long[8];
visited = new bool[80001];
memset(visited, 0, sizeof(bool) * 80001);
vis2 = new int[80001];
rvis3 = new int[80001];
pathR = (List*)malloc(sizeof(List) * 80001);
memset(pathR, 0, sizeof(List) * 80001);
//出边表
out = (List*)malloc(sizeof(List) * 80001);
memset(out, 0, sizeof(List) * 80001);
//入边表
in = (List*)malloc(sizeof(List) * 80001);
memset(in, 0, sizeof(List) * 80001);
int x, y;
for (int i = 0; i < n; ++i) {
//cin >> x >> y;
scanf("%d%d", &x, &y);
int from = x;
int to = y;
//先处理出边表
//数组
while (out[from % 79999].vaild && out[from % 79999].index != x ) {
++from;//线性
from = from % 79999;
}
out[from % 79999].index = x;
out[from % 79999].vaild = 1;
List* r = &out[from % 79999];
//数组元素的链表
while (r->next != NULL) {
r = r->next;
}
r->next = (List*)malloc(sizeof(List));
r = r->next;
r->index = y;
r->next = NULL;
r->vaild = 1;
while (out[to % 79999].vaild && out[to % 79999].index != y) {
++to;//线性
to = to % 79999;
}
out[to % 79999].index = y;
out[to % 79999].vaild = 1;
from = x;
to = y;
//处理入边表
while ( in[to % 79999].vaild&&in[to % 79999].index != y ) {
++to;//线性
to = to % 79999;
}
in[to % 79999].index = y;
in[to % 79999].vaild = 1;
r = &in[to % 79999];
//数组元素的链表
while (r->next != NULL) {
r = r->next;
}
r->next = (List*)malloc(sizeof(List));
r = r->next;
r->index = x;
r->next = NULL;
r->vaild = 1;
while (in[from % 79999].vaild && in[from % 79999].index != x) {
++from;//线性
from = from % 79999;
}
in[from % 79999].index = x;
in[from % 79999].vaild = 1;
}
for (int i = 0; i < 80000; ++i)
{
// 只有出入度不为0的结点才会构成环
//out in 的下标不一致的
//但是即使不一致也很接近的
if (out[i].vaild && in[i].vaild)
{
int p = i;
int q = i;
if (out[p].index != in[q].index) {
++q;
q = q % 79999;
while (out[p].index != in[q].index) {
++q;
q = q % 79999;
if (!in[q].vaild)break;
}
}
if (out[p].vaild && in[q].vaild && out[p].index == in[q].index) {
//tp_len = 0;
rdfs(out[p].index, out[p].index, 1);
dfs(out[p].index, out[p].index, 1);
}
}
}
if (answer == 0)answer = -1;
cout << answer;
return 0;
}