猫史档案馆


【进阶Kitten串讲】【第一期】列表的入门到进阶,Kitten实现数据结构与算法

用户:爵士OIer爵士OIer查看:26 回复:45 评论:26 创建时间:2021-01-26T18:52:12


大家好!这里是Kitten进阶教程与实战的第一期。相信跟着爵士来,大家的Kitten创作水平会大大提高。

我们将在这些教程里学习一些适合进阶深造的算法和技巧。

其实之前是发过的,当时因为太晚所以弄得很乱,而且还很多细节都没讲。

现在这篇帖子在原来的帖子基础上广为补充、发展,由一堆乱七八糟的字变成了一篇完美的教程。

 

目录:

什么是列表,基本用途

列表的插入

用列表实现数据处理

进阶算法和数据结构

前缀和算法

分块算法

 

 

 

什么是列表,基本用途

 

 

我们的Kitten上的列表,就我看来是一类线性数据,最有可能是数组。

列表内存储了一系列变量,这些变量的排列方式为线性排列。

举个最简单的例子,你将一堆苹果排成一列。把变量理解为苹果,当你要对苹果进行操作的时候,你必须在这一列中操作。

你能支持的基本操作有:任意获知一个苹果是什么样的;交换两个苹果;将一个苹果替换为一个新的苹果。

我们就要用这些基本操作来处理变量。别看这些操作非常简单,却是Kitten和语言编程中实现程序的最基本的操作和方式。

 

 

列表的插入

 

当我们需要执行一些稍复杂的运作的时候,我们只需将其组合。

这里讲解了如何插入元素。

插入元素100到第100项的积木如下:

 

center_image

 

并不难理解,大家自己画一下图就行了。

思路是把从100项开始的每一个元素都向后移一位,然后再把第100项替换为100。

下面是过程解释:

center_image

 

如果你希望更加生动的解释,看这张图center_image

 

用列表实现数据处理

 

其实我们发现在Kitten中,列表的很多内容都已经给你封装好了。

这个时候我们拖动若干积木,就能够实现一些数据处理。

列表有以下基本操作:

center_image

还可以做到获取元素

center_image

判断长度

center_image

 

我们来一个实例吧。

我们想要寻找一个列表中的最大值,怎么办?

我想很多人看到这里就已经有答案了:我们将列表中一个一个的比对过去,用一个值Max记录列表最大值。

大家看看积木怎么拼吧:

center_image

在这里,我们使用 i 遍历了整个列表,用Max记录最大值。

 

 

 

进阶算法和数据结构

 

 

这是本期教程的重点和难点。

 

问题引入

 

如何给列表的一部分 [left, right] 求和?

大家经过上面的学习,应该完成这个任务。这可能要花一点时间来思考。

我们还是用上面的遍历方法,但是这次是从 left 到 right 了。

center_image

 

 

前缀和算法

 

如果现在我要在短时间内多次求出某些区间的和,我该怎么办?

众所周知,Kitten的重复执行是由等待时间的。因此如果序列长度稍长、询问次数稍多,那么程序就要花费很长时间。

请大家思考,除了每次都向上面那样求一遍,我们还有什么方法?

不知道你们有没有这么想过:我用另一个列表 S,第 i 个位置存储从第1项到第 i 项的和。

这样做有什么好处?这是值得思考的。我想大家画画图就非常清楚了。

center_image

珂能图有点鬼,但是翻译一下就是

原序列:a1,a2,a3,a4,a5,a6,a7

我要算出:a3+a4+a5

这个就等于:a1+...+a5-(a1+a2)

同样如果我要求a4+a6,则可以转化为 a1+...+a6-(a1+...+a3)

我们看下面这张图,加深理解:

center_image

看到没有,原序列为红色线段,我要求蓝色的一段,它就等于绿色一段减去棕色一段!

上面的 S 数组就是对原数列 a 的求和。我们每次询问 Sum(left,right) 的时候,只需用S的第right项减喵的第left-1项即可

这便是我们的前缀和算法。

 

我们用Kitten实现就很简单啦,下面是建立列表 S 的过程:

center_image

 

那么我们处理询问的时候,按照上述即可。

center_image

 

分块算法

 

但是假如我们需要对序列进行修改呢?

这个时候前缀和也不管用啦,因为修改之后前缀和数组 S 也要跟着发生一连串的变化了。

怎么办?

 

以一个简单的例子为例。我们需要支持在线修改,每次给某一个数增加一个值 delta。

不知道你们有没有想到:将一系列的数捆绑在一起,算出他们的和。需要询问的时候,如果包含这些数,我们直接给出一个总和,就不需要一个个去算了。

center_image

看上面这张图。我们将数两个两个的捆绑在一起。当询问从第一个到第四个的和的时候,因为我们之前已经知道了每两个数的和,因此算的时候直接拿3+9就行了。

 

如何处理给某一个数增加 delta?

我们只需要进行两步:第一步,给这个数增加delta;第二步,给这个数所属的那一捆的总和增加 delta 即可。

 

但是有一个问题,如果要计算的不是完整的一部分怎么办?

不用动太多脑筋啦,直接一个一个累计就行了。

因此我们也要合理分配捆绑的方式。我们不能绑的个数太多,也不能一捆里面装太多的数。如果捆绑的个数多到和原序列的项数一样多,或者一捆大到包含了原来整个数列,这就相当于没有分块了。

话说回来,想到不能太多也不能太少,你想到了什么?

没错,开方!

 

因此我们将序列分为根号 n 块,要询问时整块直接累加;零碎的再单独累加。

这就是分块算法。我们容易发现具有大段维护,小段朴素的思想。

 

Code:

long long a[100010], sum[100010], add[100010];
int L[100010], R[100010]; // 每段左右端点
int pos[100010]; // 每个位置属于哪一段
int n, m, t;

void change(int x, long long d) {
	int p = pos[x];
	a[x] += d;
	sum[p] += d;
}

long long ask(int l, int r) {
	int p = pos[l], q = pos[r];
	long long ans = 0;
	if (p == q) {
		for (int i = l; i <= r; i++) ans += a[i];
		ans += add[p] * (r - l + 1);
	}
	else {
		for (int i = p + 1; i <= q - 1; i++)
			ans += sum[i] + add[i] * (R[i] - L[i] + 1);
		for (int i = l; i <= R[p]; i++) ans += a[i];
		ans += add[p] * (R[p] - l + 1);
		for (int i = L[q]; i <= r; i++) ans += a[i];
		ans += add[q] * (r - L[q] + 1);
	}
	return ans;
}

// 分块
t = sqrt(n*1.0);
for (int i = 1; i <= t; i++) {
	L[i] = (i - 1)*sqrt(n*1.0) + 1;
	R[i] = i*sqrt(n*1.0);
}
if (R[t] < n) t++, L[t] = R[t - 1] + 1, R[t] = n;
// 预处理
for (int i = 1; i <= t; i++)
	for (int j = L[i]; j <= R[i]; j++) {
		pos[j] = i;
		sum[i] += a[j];
	}

 

我想 change 和 ask 大家自己都能够写出来,主要是把分块的那部分放一下。

center_image

 

 

结语

 

本期教程就到这里了。

我们学会了列表进阶操作、前缀和算法和分块算法。

接下来一期,我们将踏入函数,学习搜索算法。

 

 


回复

上一页1 页 / 共 2下一页
爵士OIer爵士OIer

dd

点赞0


评论


爵士OIer爵士OIer

dd

点赞0


评论


真滑稽真滑稽

额。。。。。我都会啊

点赞0


评论


I桔汁糖浆II桔汁糖浆I

好家伙,非常厉害

点赞1


评论


爵士OIer爵士OIer

前缀和之前可能会有人想到过,但是是我第一次系统地提出的。

分块算法则是我第一次引入编程猫并实现的

点赞0


评论


爵士OIer爵士OIer

dd

点赞0


评论


爵士OIer爵士OIer

点赞0


评论


爵士OIer爵士OIer

dd

点赞0


评论


爵士OIer爵士OIer

dd

点赞0


评论


爵士OIer爵士OIer

dd

点赞0


评论


DHS李钛钅黑DHS李钛钅黑

╮(╯▽╰)╭楼主  我也写了个教程帖

点赞0


评论


爵士OIer爵士OIer

点赞0


评论


假编程猫258假编程猫258

好活,当赏

点赞0


评论


游侠已退游侠已退

喵,一个都看不懂

点赞0


评论


火菊火菊

666

点赞0


评论


NaughtNaught

真不戳

点赞0


评论


爵士OIer爵士OIer

dd

点赞0


评论


爵士OIer爵士OIer

dd

点赞0


评论


LogicMLogicM

针不戳

可惜我看不懂

 

点赞0


评论


白篮白篮

emotion_编程猫_点赞

点赞0


评论


珂朵莉Chtholly珂朵莉Chtholly

很有精神

点赞0


评论


_Rise__Rise_

我觉得你的字写的挺飘逸的emotion_doge

点赞2


评论


温水丶青蛙温水丶青蛙

针不戳,我竟一个也没看懂~~

点赞0


评论


CJP碧空万顷CJP碧空万顷

大补!之前觉得列表的作用就是抽奖,现在看来不止了

点赞0


评论


煤黑烧饼煤黑烧饼

tql%%%%sto jlq orz%%%巨佬

jlq AK csp

点赞0


评论


暗渐消亡暗渐消亡

懂了(原本就懂,不过作者棒呆了)

点赞0


评论


爵士OIer爵士OIer

点赞0


评论


秋韵萤兮秋韵萤兮

针不戳

点赞0


评论


The_End_404N0tF0undThe_End_404N0tF0und

太深奥了,看不懂

点赞0


评论


爵士OIer爵士OIer

dd

点赞0


评论