DS大作业

挑战一

题目描述:

        小蓝鲸开始冒险,但是异世界的征途会遇到各种妖魔鬼怪,小蓝鲸如果想要走得更远,必须提高自己的战力值。小蓝鲸为了能够存活下来并且继续冒险,来到了小怪的聚集地,决定通过打小怪的方式来积攒经验并且提升战力值。

        每当小蓝鲸击败一个小怪时,小蓝鲸的战力值都会发生改变。具体而言,每个小怪也同样具有一个战力值,假设已经有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;
}

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值