• 2020寒假qbzt游记 Day1


    基础算法

    枚举

    1.输出1~5的所有排列
    法一:next_permutation
    法二:dfs
    2.输出1~n的所有子集的子集
    一共有(3^n)
    为什么?

    U是全集,S是子集,s'是子集的子集
    每个元素有3种可能(在U里面不在S里,在S里不在s'里,在s'里)
    每次枚举,都是在判断这n个元素分别在什么位置。一个元素有3种位置,那么总的组合就是(3^ n)
    代码怎么写?
    暴力思路:第一层枚举子集,第二层枚举集合进行(check) 复杂度:(O(4^n))
    显然每次枚举再check会浪费时间
    怎么(O(3^n))解决这个问题?

    感性李姐每次-1就是生成比当前子集小的最大的集合,与当前枚举的第一层子集取交集

    搜索



    皇后可攻击她所在的行、列、对角线
    我们可以记录当前已经摆了多少个皇后,每次枚举当前行的所有格子,找到合法的格子,并标记当前列、对角线有没有被占用。

    从信息比较多的行/列开始搜(按信息量进行sort)

    适合解最优解问题(当然,要考虑内存的想法)

    用二进制表示是否有某个漏洞,利用bfs进行扩展

    一步可以推这个小方块一下(小方块可以倒下,也可以立着),问最少几步把它推进洞里

    记录当前的位置,是立着还是倒着,枚举每次操作(推到,立起来,周围的方向)

    剪枝常用:可行性剪枝(接下来不可能有解就返回),最优性剪枝(后面的状态最理想的也不会比当前搜到的解优就返回),其中最优性剪枝有些依赖搜索顺序。

    经典题:生日蛋糕

    蛋糕体积固定为(N),层数固定为(M),要求下面层的蛋糕半径比上面层大,高度比上面层大,求最小表面积(下地面除外)

    肯定是搜索辣

    那么是从下到上搜还是从上到下搜呢?

    当然是从下到上辣。因为从上到下可能填不满这个(N),而从下到上就不会

    要记录什么呢? 当前是第几层(从下到上数),体积,半径,高度。当前贡献的侧面积(因为顶面从上面看就和最底下的那层的底面积是一样的).前4个用来搜索,侧面积用来更新答案。

    搜完第(M)层时,如果体积为(N),则更新答案,否则返回。

    我们确定底层的时候要枚举(R)(H),由于最后体积是(N),所以枚举到(sqrt n)就ok了

    但是这样会(TLE),所以考虑剪枝

    可行性剪枝:

    我们可以估计一下当前状态如果填完的最小体积,最大体积,如果(N)不在这个区间内,则当前状态无解。

    最小肯定是第一层是1$ imes$1,第二层是(2 imes 2)......依次类推。最大体积:从当前层往上(R)依次-1,计算体积。当然为了复杂度也可以进行估算,预处理等乱七八糟操作

    最优性剪枝:当前侧面积+理论最小侧面积+底面积>ans就直接return (最优化剪枝一般都很强)

    代码from zrt

    #include<cstdio>
    #include<algorithm>
    
    using namespace std;
    int n,m;
    int ans;
    int small[25];
    int small_q[25];
    void dfs(int x,int v,int q, int R,int H){
    	// 已经考虑完成最下面x层 
    	// 第x层 R,H 
    	// 当前蛋糕体积为v 
    	if(v>n) return;
    	
    	if(x==m){
    		if(v==n){
    			ans = min(ans,q);
    		}
    		return ;
    	}
    	if(v + small[m-x] > n) return; 
    	if(q + small_q[m-x] >= ans) return;
    	int big = v;
    	for(int i=1;i<=m-x;i++){
    		big += (R-i)*(R-i)*(H-i);
    	}
    	if(big<n) return;
    	// x+1
    	for(int i=m-x;i<R;i++){
    		for(int j=m-x;j<H;j++){
    			dfs(x+1, i*i*j+v, 2*i*j+q,i,j);
    		}
    	}
    }
    int main(){
    	http://poj.org/problem?id=1011&lang=zh-CN
    	ans = 1e9;
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=20;i++){
    		small[i] = small[i-1] + i*i*i;
    		small_q[i] = small_q[i-1]+2*i*i;
    	}
    	// 最底一层
    	for(int i=1;i<=n;i++){
    		if(i*i>n) break;
    		for(int j=1;j<=n;j++){
    			dfs(1,i*i*j, 2*i*j + i*i,i,j);
    		}
    	} 
    	if(ans == int(1e9)) puts("0");
    	else{
    		printf("%d
    ",ans);
    	} 
    	return 0;
    }
    
    扩展

    (A^*,IDA^*)

    就是一层一层的进行dfs(为了解决bfs容易爆空间以及dfs容易TLE而创造的结合产物)

    分治

    常见算法:归并排序,快速幂,二分查找/二分答案

    就是把序列先拆开再合并

    当然它比快排稳定

    用途:卡sort时自己写,求逆序对(我只会树状数组.jpg)

    写法不怎么常见的快速幂

    ex:快(gui)速乘

    用途:防止直接乘爆longlong

    原理:(a imes n=n个a)相加

    把快速幂中的乘换成加即可

    暴力:每次(O(n))扫一遍

    优雅一点:排序之后爆扫

    再优雅一点:排序之后二分

    二分实数:

    解法一:解方程

    设半径为(r),圆心角的一半为(alpha)(L=2r sinalpha,L'=2alpha r,L'=(1+n imes c) imes L)。可以解出来但是我不会

    解法二:二分

    设偏心距为(l),上面的式子整理之后发现((1+nc)sin alpha=alpha,l=frac{L(1-cos alpha)}{2sin alpha})

    因此,我们可以二分角度来搞

    贪心

    有一堆怪,打第(i)只会掉(d_i)滴血,加(a_i)滴血,求最多打几只怪

    肯定是个贪心,但是按什么排序呢?

    (a_igeq d_i)时,肯定按(d_i)从小到大打

    (a_i<d_i)时,怎么打呢?

    1.按(d-a)从小到大

    2.按(d)从小到大

    3.按(d)从大到小

    4.按(a)从小到大

    5.按(a)从大到小

    正确方法是5

    5为什么是对的?

    先来张图李姐李姐

    这是打前半段时的血量的变化

    这是理想状态下的血线变化,当血量降到0线以下就死亡。

    我们发现这两半几乎是对称的,那么如果倒着看第二段,即把(a)(回血)看成(d)(扣血),把(d)看成(a),那么这时回血>扣血,就和第一段一样了。再正回来,就是按(a)从大到小排。

    其他几个选项的hack:

    (d-a):血量:100 (d_1=98,a_1=96,d_2=96,a_2=94)

    (d)从小到大:同上

    (d)从大到小:血量:100,(d_1=98,a_1=1,d_2=97,a_2=96)

    (a)从小到大:显然反了

    数组

    前缀和与差分

    实现区间修改

    栈:一般用数组模拟

    应用:dfs,表达式求值

    表达式求值

    重点是运算的优先级

    开两个栈,一个存数字,另一个存运算符号

    要保证符号栈中的符号优先级不递减(如果出现要递减的情况就运算)

    如果没有括号,就保证符号栈中乘号在加/减号上面。如果在一堆乘号之后出现了加号,则先运算这一堆乘号。

    那么有括号了怎么处理呢?左括号相当于分隔符,左括号之后(右括号之前)的符号的维护与之前的符号的优先级维护互不干扰,左括号内部就像上面一样维护。出现右括号就立刻弹栈,运算括号。

    有一点要注意,我们计算的时候一般是从左往右算,所以即使是同级都要计算,例如5-3-2,如果一直屯着减号就会先算3-2,再算5-1,就会wa

    右结合:乘方

    单调栈&单调队列

    单调栈:


    poj P2559

    所有矩形宽度为1,高度由输入给出,求最大子矩形的面积。

    最最暴力的方法是枚举(l,r)。复杂度(O(n^2))

    优化:我们可以先固定一个点,寻找另一个点的规律。我们发现如果高度是上升的,那么宽度就可以扩展,不需要管。如果高度下降了,那么就有可能宽度无法继续扩展。如果宽度无法继续扩展,则算一下之前的答案,否则即使是高度降低了也不进行计算。

    这样我们可以维护高度递增的栈。栈内维护二元组,记录矩形高度和款。如果有低的高度要入栈,则砍掉之前没有用的高度,进行计算。降高度的时候,可以先算一下原本的高度能造出的矩形,再降高度计算矩形面积,更新答案。当遇到一个比要入栈的高度还要小的高度时,停止弹栈,将这个高度与之前计算累计下来的宽度入栈。

    单调队列

    一般使用stl中的queue进行模拟

    用途:

    经典题:滑动窗口
    好了我们来看看单调队列

    维护当前最小(大)值对应的下标,如果这个下标“过时”了,就pop。以求最小值为例,如果当前数比前面的数小,就一直从队尾pop前面的数。最大值同理

    当然也可以在求最大值时把序列的所有数都乘-1,求最小值

    二叉搜索树

    满二叉树:全满了

    完全二叉树:最后一层的孩子可以少几个,但是少的必须是连续的。

    二叉搜索树:中序遍历(左、中、右)得到的序列有序(一般是某个节点的左子树都比他小,右子树都比他大。

    支持插入,查找大于/小于某个数的数,但是复杂度及其之不优美(依赖插入顺序)。因为插入的顺序可以把树高变成(O(n))。可以用平衡树来优化

    因为复杂度太劣了所以貌似真没什么应用。

    RMQ问题

    给一个序列,问区间最大/最小值

    ST表

    可处理没有修改的区间查询

    如果我们不会ST表怎么办?暴力,线段树,莫队

    好我们回到正题

    我们先来看看区间求最大值和区间求和的区别。

    区间最大值:不断取最大,不支持减法,可以重叠

    区间求和:前缀和,支持减法,如果有重叠要处理重叠

    ST表要处理一些区间,来使这些区间可以覆盖任意一个区间。

    那么处理哪些区间呢?

    利用区间倍增处理处([l,l+2^i])这段区间的最大值。对于每个(l),都有(log n)个区间要处理,所以总复杂度是(O(nlongn))

    如何覆盖?

    设当前区间长度为(len),左端点为(l),右端点为(r).一定可以找到一个(k),使得(2^k leq len <2^{k+1})。找([ll,l+2^k],[r-2^k,r])来覆盖这个区间。

    一般设(st[i][j])表示区间([l,l+2^j])中的最大值。

    (st[i][j]=max{[i][j-1],st[i+(1<<(j-1))][j-1]})。在覆盖的时候,我们可以算出(log_2{len})来知道(k)。由于(c++)自带的函数太慢了,所以我们进行预处理。

    覆盖:

    二维ST表

    二维区间和是用二维前缀和加加减减求得,但是求最大值不支持减法,所以我们处理每个点向右(2^i),向下(2^j)的最大值,用4个相同的小块来覆盖要求的部分

    与倍增(lca)的一点联系:处理(fa[x][i]):(fa[x][i]=fa[fa[x][i-1]][i-1])

    求欧拉序:

    欧拉序:每经过一个点,就记录一下

    炒个栗子:

    长度:(2^n-1)

    把他们的深度记下来:

    深度似乎和(lca)有点联系

    一段区间里深度最小的点是这一坨点的(lca)

    我怕不是学了假的欧拉序

    可以再用ST表记录取到最小值的位置,然后就可以找到(lca)了。

    因为这里每次深度要么+1,要么-1,所以也叫±1(RMQ)

    有许多种欧拉序,这只是其中的一种

    并查集

    支持:合并集合,查询两个集合是否在同一集合中

    按秩合并:

    记录一个(siz)表示集合的大小,由小的集合连向大的集合比大的连小的要更优(有更少的元素找根需要多走一条边)。

    初始的(siz)是个随机数(pwq)

    路径压缩:查询的时候直接把父节点,祖父节点,曾祖父节点......直接接到根上

    启发式合并:


    显然把小的数组塞到大数组里面要好一些。每次合并之后的大小是原来的小集合大小的2倍以上。这样看,最多合并(log n)次,每次合并是(O(n))的,所以总复杂度是(O(nlogn))

  • 相关阅读:
    野火书籍《STM32库开发实战指南》配套例程
    使你的MFC程序具有win7的透明立体风格
    C#:params 关键字的学习
    "ASP.NET Ajax 客户端框架未能加载"解决办法
    未能启用约束。一行或多行中包含违反非空、唯一或外键约束的值。
    GIS:如何将东城和崇文合并
    委托和Lambda表达式(一):委托概述
    Cron表达式详解
    正则表达式在JAVA中的简单实例
    使用jdom创建XML文件
  • 原文地址:https://www.cnblogs.com/lcez56jsy/p/12207020.html
Copyright © 2020-2023  润新知