基础重修笔记

2020.11.16

今天学的东西比较多,由于是按照紫书上的顺序来学的,就稍微整理一下。

  • 栈与队列

  • 链表

  • 树和二叉树

栈和队列

关于队列的例题里就一道,关于双向队列,而且题意实在是看不懂,被迫放弃了。(普通队列和优先队列我应该还熟吧)

关于栈的题做了两道,感觉运用能力提高了一些。栈这东西貌似在对付括号这方面有奇效,有时间要去做一下括号树了。栈的定义很简单,就是一个LIFO表,即Last in First out,后进先出的意思。主要得学会应用。

一道例题:UVA442 矩阵链乘

链表

感觉上学的并不是很懂,不过有点似懂非懂的意味?其实大概也就是用一个特殊的数组把所有数组内的东西串联起来,可以更方便移动数组内的元素。不过说实话,链式前向星应当也是一种链表,感觉就是这样。

具体的东西还是要看代码实现。嘴上说说作用似乎不大,所以就放一道双向链表的经典例题在这吧:UVA12657 移动盒子

至于应用,其实我也说了,一般只有要求要大量移动数组内的元素的题才用得上链表,至少我做的两道例题都是如此,也许明天做的习题会有新花样?

树与二叉树

1.二叉树的编号

嗯,总之记住一个基本的东西,就是左子树编号=根的编号×2,右子树编号=根的编号×2+1。单考编号的话应该考不到很难,只需熟练地掌握基本性质即可。当然,一定的分析能力必不可少,这就需要花时间去提升了。

2.二叉树的层次遍历

一堆指针,直接被劝退,等我哪一天学好了指针再说吧……

3.二叉树的递归遍历

二叉树的3种遍历方式:先序遍历,中序遍历以及后序遍历。之前看白书的时候怎么看都看不懂,现在用递归版的定义就清晰明了的多了。

Preorder(T)=T的根节点+Preorder(T的左子树)+Preorder(T的右子树)

Inorder(T)=Inorder(T的左子树)+T的根节点+Inorder(T的右子树)

Postorder(T)=Postorder(T的左子树)+Postorder(T的右子树)+T的根节点

本小节有三道例题,都是对递归非常好的应用,应当掌握。

4.非二叉树

平时会碰到的题大部分都是二叉树的,非二叉树实在太少,就随意讲了。非二叉树的题给你的树一般也不是随便的,可能也就是三叉树,四叉树那样的,能熟练掌握递归建树就行了,大概就这一个要点。

1.用DFS求联通块

感觉跟求强连通分量差不多,只要对于每一个特殊地形,都用一次深度优先遍历将整张图都跑一遍,同时注意去重就行了。

2.用BFS求最短路

这个最简单的用法就不说了,我早就会了。还是看看更难的例题吧:UVA816 Abbott的复仇

实际题目中要求会更多,比如朝向之类的状态可能会出现,但其实也不用太担心,对于每一种状态都分别处理好就行了,只是有时可能真的会麻烦一点。

3.拓扑排序

例题太水了不说。当时学缩点的时候就顺便学了,笔记在图论那一块有,翻翻就是了。

4.欧拉回路

看了看,感觉好像也没什么,没有想象中难。

有欧拉回路或欧拉道路的一个先决条件是图联通,这个DFS一遍即可判断。其次要记录每个点的入度与出度。

由于欧拉回路与欧拉道路的特殊性,它要求最多只能有两个奇点,即度数为奇数的点。这两个奇点分别是欧拉道路的起点与终点。

起点的出度比入度大1,终点的入度比出度大1。如果没有奇点,就可以从任何点出发,最终都可以回到该点,即为欧拉回路。


2020.11.17

今天没有学新的知识,只是在做昨天学的东西的习题。感觉思维还有待提升,不看题解大概也就做一些比较裸的题,数据结构还是比较难的。尤其是书上的习题,感觉都很难。

主要是UVa全是字符串,实在过于难受,输入输出都搞得很麻烦,而且UVa天天多组数据还不告诉你数据组数,属实可恶,至于卡格式更是UVa特色。

做了几道简单一点的习题,收获比较大的大概是求先后序排列?不过这东西可能也就初赛会考考,复赛应该不会考这么冷门的东西,紫书有些东西实在是过时了。

找了以前的弱省省选题做了一些,做的都是很简单的,一遍过,发现以前的省选里简单题还确实是简单的。虽然真的是很久以前的省选题了

明天开始学新知识了,希望书上的暴力求解法对比赛真的有用吧。

最后感叹,生活在一个没有字符串的国度实在是太好了。该死的UVa就不能多用用阿拉伯数字吗

2020.11.18

开始学习暴力求解专题,学了很多新东西,其实并没有。跟着书上做了几道简单枚举题,发现有时候无脑枚举也不可行,稍微思考一下可以大大减少枚举量。

学会了枚举全排列,生成子集,感觉以后可能会用到,位运算的方法比较新颖独特,代码放在这里。

void print_subset(int n,int s)
{
	for(int i=0;i<n;i++)
	 if(s&(1<<i))printf("%d ",i);
	printf("\n");
}
int main()
{
	for(int i=0;i<(1<<n);i++)
	print_subset(n,i);
}

利用此代码可以生成0~n-1的所有子序列。

枚举排列只要会用这个STL就行了

next_permutation(p,p+n);

可以直接得到p的下一个排列,就是不知道考场上能不能用。听说带下划线的库函数都不能用?

顺带重修了dfs。所以说,暴力求解法要是没有回溯法就不叫暴力了。果然dfs是暴力的归宿啊。

做了做书上的例题,有的还需要剪枝,或者跟树混在一起,所以说,树这东西真的不想碰到,一碰到问题就会变得麻烦起来。

不过树也就那样,毕竟只有左子树右子树,每次分类讨论还是挺清晰的。

还重修了bfs,发现这和原来提高篇的bfs小节有点像,感觉高级的一些bfs也就是把状态弄的复杂了一点,之前二维的xy给你弄成八维九维的,当然不可能让你爆空间,所以每个维度的可选物都很少,但是指数级别就完全开不下,而且浪费很多。

典型例题:P1379 八数码难题

会将状态进行压缩就行了,将一个数组压成一个数字,便于去重。压缩方式的话,使用哈希函数是最好的了,当然还要解决冲突,这题也可以用set去重,但是用STL虽然方便,效率却差的多,亲测慢了近3s,学会用哈希判重还是很有必要的。

提高篇上的例题是这道:P2730 [USACO3.2]魔板

其实完全没有区别,就是操作不一样罢了,状态压缩是完全一致的。对付这种枚举排列的题有一个完美哈希,即不会产生冲突的哈希,那就是使用康托展开的技巧,详情可见模板题:P5367 【模板】康托展开

事实证明提高篇比起紫书还是差远了,上来就一个康托展开,标题写着Hash判重但是完全没有提及,结果我没头没脑的学了个康托展开就过了,相比之下紫书给了三种不同的解法,分别用康托,哈希表和set,讲的很清楚,孰优孰劣一目了然。

写了会例题后就去找了点水题做做,感觉花样还是很多的。比如发现了一个有三维状态的题,数据范围小,不用压缩,但是写的比较累。

然后还尝试了多个点同时拓展,感觉比较新颖,题目放这:P1332 血色先锋队。思路其实也比较简单,其实只要用vector一口气把所有队列中的结点都存下来,然后再一个个循环拓展就行了,完全不会有问题。

顺带水了一道dfs求联通块的题,复习了一下新知识。

今日吐槽

主题库评测就是快,不像提交UVa的题库,问题天天有,运气好可能几秒,运气差几分钟,运气再差可能就永远waiting了。更别提卡格式,多组数据,字符串处理等等毛病。多一个空行就WA,我也是醉了。

补充:

今天被wzy安利去做CF的题,感觉ABC都是很简单的题,但是有时那一下弯就很难转过来,也许下个星期会去找点CF的简单题来做,应该可以锻炼到我的思维。

还是想说:就算是CF速度也比UVa快多了,都和主题库速度一样了!都是外国的网站差距也太大了吧!

希望能早日脱离UVa的苦海啊。

2020.11.19

差点忘记还要写博客了。今天又是刷水题的一天。

开始找UVa恶心自己前先水了几道CF的A题,感觉自己可以了。然后看着题解把一道状态压缩bfs打过去了,格式日常被卡好几回,居然还要输入换行符才能过得了,见识到了。

scanf("%d%d\n",&n,&m);

不加\n居然会RE,这是什么神仙错误,记下来记下来。

开始学习迭代加深搜索,感觉还是比较简单,和估价函数配合起来就会变成IDA*算法,感觉都不算难。但是估价函数有时候没那么好推。(而且要处理题目那千奇百怪的状态真的好难啊)

做了一道被恶评的裸IDA,传送门在此:UVA1374 快速幂计算。还有双倍经验,但是是绿的。然后去找了点IDA的题,都是蓝的,但是都好难啊。果然是恶评

明天还要多做点IDA*的题,但是真的想早点脱离搜索的苦海了。状态压缩真的好烦啊。

虽说如此搜索仍然不得不学,而且还得好好学。所以就放一个迭代加深的板子在这,当然仅供参考。

int IDA(int d)
{
	if(d+h(x)>maxd)return 0;
	if(d==n)return 1;//(满足条件)
	for(int i=1;i<=n;i++)
	{
		if((IDA(d+1)))return 1;
	}
	return 0;
}
int main()
{
	//do something
	for(maxd=0;;maxd++)if(IDA(0))break;
	cout<<maxd;
}

真的仅供参考,尤其是函数部分,真要应用起来哪有这么简单……也就主函数里的比较模板一点了。

然后晚上不想做IDA*了,就去刷搜索水题,发现自己居然连绿题也刷不动了。居然要dfs+dp,你不是搜索题

看题解发现还有一道弱化版的,不用dfs枚举的dp题,发现是道背包,于是就A掉了,还顺带发现并做掉了一道类似的NOIP2018day1T2。

绕了一圈回过头来做这道最开始的搜索题,dp部分很愉快的就打过去了,结果没过样例,调调调发现没清空dp数组,无语。

改了之后交一发,TLE了,遂改了一下枚举方式,结果只多过了一个点。无奈之下学习题解,改了一下发现A掉了,快了非常多。

方法值得学习,所以代码就放在这里。

以前我都是这么写的

for(int i=1;i<=n;i++)
{
    if(!ex[i])
    {
		ex[i]=1;
		dfs(now+1);
		ex[i]=0;
    }
}

TLE后改成这样了。

for(int i=last+1;i<=n;i++)
{
	ex[i]=1;
	last=i;
	dfs(now+1);
	ex[i]=0;
}

结果A了。

我左思右想也没想通,我不就让程序多判断了几次,而且 \(n\le20\) ,这能多出多少枚举量来?

事实证明我错的离谱,这么小的数据范围也能增这么多枚举量,看来以后要改变习惯。

不要忽视任何一个优化,因为你不知道它将给你减少多少万次循环。--沃兹基·朔德。

今日总结:思维依旧严重不足,感觉一天天的大脑都快要生锈了,大概也就是能水一水普及-的题?没学过可能还不一定能过得了

2020.11.20

今天划水现象严重,在此严厉批评。

上午使用A*算法做了两道k短路,模板是黑的,所以今天A了第一道黑题(可喜可贺)。近11点写完两道题就去逛水区了,结果剩余时间只水了3道CF简单题完成任务,应当反省。

用A*解k短路问题思路如下。

首先要知道k短路如何求,我们知道一般的优先队列Dij求最短路,第一次遇到终点时就是最短路,那么其实当我们第k次将终点入队时,得到的就是到终点的第k短路径。

但是我们直接去求是一定会超时的,这时候就要用到A*算法的估价函数来减少循环次数,优先级的判断需要转换,我们需要先求出终点到其他所有点的最短路径,用数组f存储,然后优先级处理时用当前距离dis加上预估距离f来判断。

因为f已经是从此点到终点的最短路径,所以估价函数设计正确,实际长度不会比估计长度更小。

注意:估价函数剪枝效果非常不稳定,虽然大多数时候不会TLE,但是会因为队列内存储状态过多而MLE。简而言之,A*算法和SPFA一样都会被卡,但是卡的力度并不很大,在洛谷的题中都只有一个点来卡,一般可以获得90分,必须打表才可过。

代码就不放了,可以到模板题去看。

下午做题没有方向,划水划了一个小时,不想写IDA*,又不知道学什么好。于是就稍微看了看蓝书和黑书中关于约数的数学知识,并做了几道题。

稍微整理一些基础数学知识。

算术基本定理

任意一个大于 \(1\) 的正整数都能唯一分解为有限个质数的乘积,可写作:

\[N=p_1^{c_1}p_2^{c_2}\dots p_m^{c_m} \]

其中 \(c_i\) 都是正整数, \(p_i\) 都是质数,且满足 $p_1<p_2<\dots<p_m $。

试除法分解质因数代码实现

void divide
{
	m=0;
	for(int i=2;i*i<=n;i++)
	{
		if(n%i==0)
		{
			p[++m]=i,c[m]=0;
			while(n%i==0)n/=i,c[m]++;
		}
	}
	if(n>1)p[++m]=n,c[m]=1;
	for(int i=1;i<=m;i++)
	cout<<p[i]<<'^'<<c[i]<<endl;
}

由算术基本定理可推得,N的正约数集合为:

\[{p_1^{b_1}p_2^{b_2}\dots p_m^{b_m}},\text{其中}0\le b_i\le c_i \]

N的正约数个数为

\[(c_1+1)*(c_2+1)*\ldots *(c_m+1)=\prod\limits_{i=1}^m(c_i+1) \]

N的所有正约数的和为

\[(1+p_1+p_1^2+\ldots +p_1^{c_1}*\ldots *(1+p_m+p_m^2+\ldots +p_m^{c_m})=\prod\limits_{i=1}^m(\sum\limits_{j=0}^{c_i}(p_i)^j) \]

求正约数个数的公式用的比较多。

例题:反素数

晚上还划了很久的水,需要好好反省。就没做什么题了,做了道位运算。

反省结果就是没有明确目标,于是剩余时间就一直在搞之后的学习计划与任务清单,好在终于搞完了。

总之,紫书的部分已经完结了,马上要告别UVa好耶,投入到更多的题目中去了。

完结,明天会开新的一章。

posted @ 2020-11-30 19:48  洛桃  阅读(109)  评论(0编辑  收藏  举报