第六周算法题(区间合并,双序构造二叉树,二分,染色,dfs)

第六周

第一题:

题目来源:3729. 改变数组元素 - AcWing题库

给定一个空数组 V和一个整数数组 a1,a2,…,an。

现在要对数组 V 进行 n次操作。

第 i 次操作的具体流程如下:

  1. 从数组 V 尾部插入整数 00。
  2. 将位于数组 V 末尾的 ai 个元素都变为 1(已经是 1 的不予理会)。

注意:

  • ai 可能为 0,即不做任何改变。
  • ai 可能大于目前数组 V 所包含的元素个数,此时视为将数组内所有元素变为 1。

请你输出所有操作完成后的数组 V。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。

每组数据第一行包含整数 n。

第二行包含 n个整数 a1,a2,…,an。

输出格式

每组数据输出一行结果,表示所有操作完成后的数组 V,数组内元素之间用空格隔开。

数据范围

1≤T≤20000,
1≤n≤2×10^5,
0≤ai≤n,
保证一个测试点内所有 n 的和不超过 2×10^5。

输入样例:
3
6
0 3 0 0 1 3
10
0 0 0 1 0 5 0 0 0 2
3
0 0 0
输出样例:
1 1 0 1 1 1
0 1 1 1 1 1 0 0 1 1
0 0 0

解题代码:

#include <stdio.h>
int main() {
    int n;
    scanf("%d", &n);
    while (n--) {
        int m;
        scanf("%d", &m);
        int arr[m], ans[m];
        for (int i = 0; i < m; i++) scanf("%d", &arr[i]);
        int j = m - 1, idx = 0;
        while (j >= 0) {
            if (!arr[j]) ans[idx++] = 0, j--;
            else {
                int k = arr[j];
                while (1) {
                    int index = j, end = index - k;
                    for (j; j > end && j >= 0; j--) {
                        if (arr[j] > k) {
                            k = arr[j];
                            break;
                        }
                        else {
                            ans[idx++] = 1;
                        }
                        k--;
                    }
                    if (j < 0 || j == end) {
                        break;
                    }
                }
            }
        }
        for (int i = idx - 1; i >= 0; i--) printf("%d ", ans[i]);
        printf("\n");
    }
    return 0;
}

解题思路:

我自己也不知道这是什么算法,但可以肯定的是它的时间复杂度是o(n),为什么呢,因为本题实际上只用了一个尾指针向前扫描,并且该指针没有回溯,因此时间复杂度应该是扫描n个一遍的时间,所以o(n),大概思路是:我们可以知道,它后面的操作会覆盖掉前面的操作,而前面一定不会影响到后面,那我们从后往前扫描,最后反转结果就行,如果某一位非0为n,那么从此位起至前n位一定为1,不用看,只需要处理其中大于n的数,及时更新就行。

第二题:

题目来源:1497. 树的遍历 - AcWing题库

题目描述:

一个二叉树,树中每个节点的权值互不相同。现在给出它的后序遍历和中序遍历,请你输出它的层序遍历。

输入格式

第一行包含整数 N,表示二叉树的节点数。

第二行包含 N 个整数,表示二叉树的后序遍历。

第三行包含 N 个整数,表示二叉树的中序遍历。

输出格式

输出一行 N个整数,表示二叉树的层序遍历。

数据范围

1≤N≤30
官方并未给出各节点权值的取值范围,为方便起见,在本网站范围取为 1~N。

输入样例:
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
输出样例:
4 1 6 3 5 7 2

解题代码:

#include <stdio.h>
#include <stdlib.h>
#define N 60
struct tree {
	int data;
	struct tree* lchild;
	struct tree* rchild;
};
struct queue {
	struct tree* data[N];
	int front;
	int rear;
};
int postIndex;
int find(int data, int arr[], int start, int end) {
	int i;
	for (i = start; i <= end; i++) {
		if (arr[i] == data)
			return i;
	}
}
struct tree* createtree(int *postorder,int *inorder,int begin,int end) {
	if (begin > end) {
		return NULL;
	}
	struct tree *node = (struct tree*)malloc(sizeof(struct tree));
	node->data = postorder[postIndex--];
	node->lchild = NULL;
	node->rchild = NULL;
	int idx = find(node->data, inorder, begin, end);
	node->rchild = createtree(postorder, inorder, idx + 1, end);
	node->lchild = createtree(postorder, inorder, begin, idx - 1);
	return node;
}
void leverorder(struct tree* p) {
    struct queue q;
    q.front = 0;
    q.rear = 0;
    if (p != NULL) q.data[q.rear++] = p;
    while (q.front != q.rear) {
        struct tree* node = q.data[q.front++];
        printf("%d ", node->data);
        if (node->lchild != NULL) q.data[q.rear++] = node->lchild;
        if (node->rchild != NULL) q.data[q.rear++] = node->rchild;
    }
}
int main() {
	int n;
	scanf("%d",&n);
	int postorder[N];
	int inorder[N];
	for (int i = 0; i < n; i++) {
		scanf("%d",&postorder[i]);
	}
	for (int i = 0; i < n; i++) {
		scanf("%d",&inorder[i]);
	}
	postIndex = n - 1;
	struct tree *root = createtree(postorder,inorder,0,n-1);
	leverorder(root);
	return 0;
}

解题思路:

非常暴力了,一种很简单的思路,就是利用两种遍历结果,真正构造一棵二叉树,然后再用队列进行层序遍历即可,我们很容易知道,对于后序遍历,那么它最后一个节点一定是根节点,那我们我们可以在中序遍历中找到这个根节点,并且中序遍历的顺序是左中右,那么在该节点左边就是左子树,在它右边就是右子树,对于左子树而言,在后序遍历中,左子树的那一部分的最后一个节点又是左子树的根节点,即可以用递归的方式建立一棵二叉树,就自然而然能够进行层序遍历了。

第三题:

题目来源:1460. 我在哪? - AcWing题库

题目描述:

农夫约翰出门沿着马路散步,但是他现在发现自己可能迷路了!

沿路有一排共 N个农场。

不幸的是农场并没有编号,这使得约翰难以分辨他在这条路上所处的位置。

然而,每个农场都沿路设有一个彩色的邮箱,所以约翰希望能够通过查看最近的几个邮箱的颜色来唯一确定他所在的位置。

每个邮箱的颜色用 A…Z之间的一个字母来指定,所以沿着道路的 N个邮箱的序列可以用一个长为 N的由字母 A…Z组成的字符串来表示。

某些邮箱可能会有相同的颜色。

约翰想要知道最小的 K 的值,使得他查看任意连续 K个邮箱序列,他都可以唯一确定这一序列在道路上的位置。

例如,假设沿路的邮箱序列为 ABCDABC

约翰不能令 K=3,因为如果他看到了 ABC,则沿路有两个这一连续颜色序列可能所在的位置。

最小可行的 K的值为 K=4,因为如果他查看任意连续 4 个邮箱,那么可得到的连续颜色序列可以唯一确定他在道路上的位置。

输入格式

输入的第一行包含 N,第二行包含一个由 N个字符组成的字符串,每个字符均在 A…Z 之内。

输出格式

输出一行,包含一个整数,为可以解决农夫约翰的问题的最小 K值。

数据范围

1≤N≤100

输入样例:
7
ABCDABC
输出样例:
4

解题代码:

一:
#include <stdio.h>
int main() {
	int n;
	char str[1001];
	scanf("%d\n", &n);
	gets(str);
	for (int k = 1; k <= n; k++) {
		int idx = 0;
		for (int i = 0; i + k - 1 < n; i++) {
			for (int j = i + 1; j + k - 1 < n; j++) {
				int index = 1;
				for (int q = 0; q < k; q++){
					if (str[i + q] != str[q + j]) {
						index = 0;//不相同
						break;
					}
			}
				if (index) {
					idx = 1;//相同
					break;
				}
			}
			if (idx) {
				break;
			}
		}
		if (!idx) {
			printf("%d", k);
			return 0;
		}
	}
	return 0;
}
二:
#include <stdio.h>
char str[1000];
int n;
int check(int k,int idx) {
	for (int i = 0; i + k - 1 < n; i++) {
		for (int j = i + 1; j + k - 1 < n; j++) {
			int same = 1;
			for (int m = 0; m < k; m++) {
				if (str[m + i] != str[m + j]) {
					same = 0;
					break;
				}
			}
			if (same) {
				idx = 0;
				break;
			}
		}
	}
	return idx;
}
int main() {
	scanf("%d\n", &n);
	gets(str);
	int l = 1, r = n;
	while (l<r) {
		int mid = l + r >> 1;
		if (check(mid,1)) {
			r = mid;
		}
		else {
			l = mid+1;
		}
	}
	printf("%d", l);
	return 0;
}
三:
#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;
int n;
string str;
bool check(int mid) {
	unordered_set<string>hash;
	for (int i = 0; i + mid - 1 < n; i++) {
		if (hash.count(str.substr(i, mid))) {
			return false;
		}
		hash.insert(str.substr(i, mid));
	}
	return true;
}
int main() {
	cin >> n >> str;
	int l = 1, r = n;
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) {
			r = mid;
		}
		else {
			l = mid + 1;
		}
	}
	cout << r << endl;
	return 0;
}

解题思路:

用了三种方法来做这个题,第一种就是经典的暴力,时间复杂度大概是n的四次方,第二种做法则是用了一层二分查找,我们可以知道,如果k是正确答案,那么k到n一定也是正确答案,1到k一定不是正确答案,符合二段性,所以可以用二分,大概时间复杂度是n的三次方的logN次方,第三种方法,用到了一个哈希表,通过对字符串是否存在的查询,再加上二分的搜索,使时间复杂度控制在N的logN次方。

第四题:

题目来源:P1162 填涂颜色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

由数字 0组成的方阵中,有一任意形状的由数字 1 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 2。例如:6×6的方阵(n=6),涂色前和涂色后的方阵如下:

如果从某个 0 出发,只向上下左右 4 个方向移动且仅经过其他 0 的情况下,无法到达方阵的边界,就认为这个 0 在闭合圈内。闭合圈不一定是环形的,可以是任意形状,但保证闭合圈内的 0 是连通的(两两之间可以相互到达)。

0 0 0 0 0 0
0 0 0 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 1 0 1
1 1 1 1 1 1
0 0 0 0 0 0
0 0 0 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 1 2 1
1 1 1 1 1 1

输入格式

每组测试数据第一行一个整数 n(1≤n≤30)。

接下来 n 行,由 0 和 1组成的 n×n 的方阵。

方阵内只有一个闭合圈,圈内至少有一个 0。

输出格式

已经填好数字 2 的完整方阵。

输入输出样例

**输入 **

6
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1

**输出 **

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

说明/提示

对于 100 的数据,1≤n≤30

解题代码:

#include <stdio.h>
int x_m[4] = { 0,0,1,-1 };
int y_m[4] = { 1,-1,0,0 };
int n;
int arr[31][31];
void search(int x,int y) {
	arr[x][y] = 3;
	for (int k = 0; k < 4; k++) {
		int x0 = x + x_m[k];
		int y0 = y + y_m[k];
		if (x0 >= 0 && x0 < n && y0 >= 0 && y0 < n && arr[x0][y0] == 0) {
			search(x0, y0);
		}
	}
}
int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			scanf("%d", &arr[i][j]);
		}
	}
	for (int i = 0; i < n; i++) {
		if (arr[i][0] == 0) {
			search(i, 0);
		}
		if (arr[i][n - 1] == 0) {
			search(i, n - 1);
		}
	}
	for (int j = 0; j < n; j++) {
		if (arr[0][j] == 0) {
			search(0, j);
		}
		if (arr[n - 1][j]==0) {
			search(n - 1, j);
		}
	}

for (int i = 0; i < n; i++) {
	for (int j = 0; j < n; j++) {
		if (arr[i][j] == 0) {
			arr[i][j] = 2;
		}
		if (arr[i][j] == 3) {
			arr[i][j] = 0;
		}
	}
}
for (int i = 0; i < n; i++) {
	for (int j = 0; j < n; j++) {
		printf("%d ", arr[i][j]);
	}
	printf("\n");
}
return 0;

}

解题思路:

  1. 变量定义x_my_m 是用于在四个方向(上、下、左、右)上移动的数组。n 是二维数组 arr 的大小,arr 用于存储输入的二维网格。
  2. 搜索函数search 函数是一个递归函数,用于执行深度优先搜索。它首先将当前位置标记为 3,然后在四个方向上进行搜索。如果新的位置在网格内并且值为 0,则对新位置进行搜索。
  3. 主函数main 函数首先读取网格的大小 n,然后读取 n*n 的网格。然后,它在四个边缘上的每个位置开始搜索,如果该位置的值为 0,则调用 search 函数。最后,它遍历整个网格,将所有未被访问的位置(值为 0)标记为 2,并将所有被访问的位置(值为 3)标记回 0。然后,它打印出最后的网格。
用了染色法,大致思路如下,已知被1围成一圈的0要改为2,不妨把没有被1围住的零先改成3,然后最后遍历一次,把剩下的零0改成2,把3改回0,首先,定义上下左右移动的数组,然后从两种讨论,第一种是第一排和最后一排,第二种是第一列和最后一列,然后不断的向里面渗透,即完成染色。

第五题:

题目来源:P1141 01迷宫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述:

有一个仅由数字 0 与 1 组成的 n×n 格迷宫。若你位于一格 0上,那么你可以移动到相邻 4 格中的某一格 1上,同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上。

你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。

输入格式

第一行为两个正整数 n,m

下面 n 行,每行 n 个字符,字符只可能是 0 或者 1,字符之间没有空格。

接下来 m 行,每行两个用空格分隔的正整数 i,j,对应了迷宫中第 i 行第 j 列的一个格子,询问从这一格开始能移动到多少格。

输出格式

m 行,对于每个询问输出相应答案。

输入输出样例

**输入 **

2 2
01
10
1 1
2 2

**输出 **

4
4

说明/提示

对于样例,所有格子互相可达。

  • 对于 20%的数据,n*≤10;
  • 对于 40%的数据,n*≤50;
  • 对于 50%的数据,m*≤5;
  • 对于 60%的数据,n*,m≤100;
  • 对于 100%的数据,1≤n≤1000,1≤m≤100000。

解题代码:

#include <stdio.h>
#include <string.h>
int n, m;
int arr[1005][1005];
int state[1005][1005];
int x_m[4] = { 1,-1,0,0 };
int y_m[4] = { 0,0,1,-1 };
int sum = 0;
int ans[1005][1005] = { 0 };
int idx = 0;
void search(int x, int y) {
    if (ans[x][y]) {
        sum = ans[x][y];
        return;
    }
    sum++;
    state[x][y] = idx;
    for (int k = 0; k < 4; k++) {
        int x0 = x + x_m[k];
        int y0 = y + y_m[k];
        if (x0 > 0 && x0 <= n && y0 > 0 && y0 <= n && state[x0][y0] != idx && arr[x0][y0] != arr[x][y]) {
            search(x0, y0);
        }
    }
}
void get_ans(int x, int y, int u) {
    state[x][y] = idx;
    ans[x][y] = u;
    for (int k = 0; k < 4; k++) {
        int x0 = x + x_m[k];
        int y0 = y + y_m[k];
        if (x0 > 0 && x0 <= n && y0 > 0 && y0 <= n && state[x0][y0] != idx && arr[x0][y0] != arr[x][y]) {
            get_ans(x0, y0,u);
        }
    }
}
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            scanf("%1d", &arr[i][j]);
        }
    }
while (m--) {
    int i, j;
    scanf("%d %d", &i, &j);
    if (ans[i][j]) {
        printf("%d\n", ans[i][j]);
        continue;
    }
    sum = 0;
    idx++;
    search(i, j);
    int u = sum;
    printf("%d\n", sum);
    idx++;
    get_ans(i, j, u);
}
return 0;
}

解题思路:

本题的样例范围过大,如果只是单纯的利用dfs,会有三个样例过不了,那我们再观察本题,我们可以发现,点之间具有连通性,通俗来讲,如果某一个点,它能走的最大路径是四,那么,在这个路径上的每一个点,他所能经过的最大路径也必然为四,所以我们可以利用一个数组来储存答案。
我们首先定义一个上下左右移动的数组,对于m次操作,每一次操作,我们使用dfs算法,从该点的位置进行上下左右试探移动,每一次成功移动,我们用一个游标idx,进行状态标记,我们记录它连续移动的次数,注意,因为有idx的标记,所以它无法移动到已经经过的位置,最关键的一点,Idx是一个全局变量,而它的赋值,是在dfs算法的外部,因此,在一次函数调用时,它的idx是一定为一个固定的值,这是一个很关键的地方,然后当search函数结束时,我们就得到了它的最大路径。
接着,我们使用第二个函数来记录答案,我们首先将idx+1,几乎是重复search函数的操作,对同一个起始坐标进行路径赋值,这样下次再继续查询时,我们可以快速判断答案数组是否为零,如果在答案数组中,该坐标已经有一个值,那我们可以直接返回此值,不再进行重复移动。
如果本题没有idx这个变量,那么,他还是仍然有两个样例过不了,因为状态数组要进行回溯,而回溯的实现,如果用单纯的遍历赋值,或者说是memset,相当于又给他增加了一个乘数n,而两个函数都会用到状态数组,因此,会给时间复杂度增加2n,而如果用idx,它在两个函数的外部进行不断增加一的操作,在每一个函数中,只用比较状态数组的值和当前idx的值是否相等即可,即为函数节省了大量的时间。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xiao Ling.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值