zoukankan      html  css  js  c++  java
  • 填坑帖

    填一填之前没学或是没学好的

    基本内容的话

    1.倍增,ST算法

    2.单调栈,单调队列

    3.二进制状态压缩

    倍增

    倍增,就是“成倍增长”。指我们在进行递推时,如果状态空间很大,通常的线性递推无法满足时间与孔家复杂度的要求,那么我们可以通过成倍增长的方式,只递推状态空间中在2的整数次幂
    位置上的值作为代表。当需要其他位置上的值时,我们通过“任意整数可以表示成若干个2的次幂项的和”这一性质,使用之前求出的代表值拼成所需的值。
    因此使用倍增算法需要状态空间关于2的次幂具有可划分性
    “倍增”和“二进制划分”两个思想互相结合,可以降低求解很多问题的时间与空间复杂度

    例题:
    给定一个长度为N的数列A,然后进行若干次询问,每次给定一个整数T,求出最大的k,满足1~k的a[i]的累加和不大于T。算法必须是在线的(即必须即时回答每一次询问,不能等待收到
    所有询问后再统一处理)
    最朴素的做法显然是从前向后枚举K,每次询问花费的时间与答案的大小有关,最坏情况下为O(n)
    我们O(n)预处理出前缀和显然也可以O(n)求解
    但是当我预处理出前缀和后,就可以二分出k的位置,预处理复杂度O(n),二分复杂度O(logn),这个算法在平均情况下表现是很好的,然而缺点是如果每次询问给出的整数T都非常小,那么
    必然造成答案k也非常小,有时还不如直接扫前缀和来得快,甚至不如从前向后枚举优
    所以我们想怎么优化一下,当然,我们仍旧要前缀和,但是可以丢弃二分,设计一种倍增算法
    1.另k = 0, p = 1, sum = 0
    2.比较A数组中k之后的p个数的和与T的关系,若sum + s[k+p] - s[k] <= T,说明这k个数可以累加到答案上,sum += s[k+p] - s[k], k+=p, p*=2.即累加上这p个数的和,然后把p
    的跨度增长1倍,当sum + s[k+p] - s[k] > T时,说明我们不能累加上这k个数,令p /= 2
    3.重复上述操作,知道p == 0,此时的k就是答案,(当 p == 1时,表示这是要累加一个数,如果这个数都不能累加,说明此时k个数的和达到最大了,因为是求前缀的,所以肯定k也是最大的)
    该算法始终在答案大小的范围内实施“倍增”与“二进制划分”思想,通过若干长度为2的次幂的区间拼出最后的k,时间复杂度并不比二分差,又因为该算法进行倍增始终在答案大小范围内
    所以我个人觉得比二分优,尽管一群学长依旧力荐二分,=-=。不过二分还是超棒的啊,O(logn)不知道比O(n)优秀到哪里去了。

    例题2 Genius ACM hihocoder#1384
    给定一个整数M,对于任意一个整数集合S,定义“校验值”如下:
    从集合S中取出M对数(即2*M个数,不能重复使用集合中的数,如果S中的整数不够M对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合S的“校验值”
    给定一个长度为N的数列A以及一个整数T。我们要把A分成若干段,使得每一段的校验值都不超过T。求最少需要分成几段

    利用贪心的思想,先对问题进行分析
    要求每对数的差的平方和最大,显然应该取集合S中最大的M个数和最小的M个数,我们想把集合的最大值和最小值构成一对,次大值和次小值构成一堆....这样求出的“校验值”肯定是最大的。
    而为了让A划分的段数尽量少,我们要让每一段中尽可能塞下更多的数,让每一段都尽可能长,到达结尾时分成的段数就是答案
    于是需要解决的问题就是:当确定一个左端点L,右端点R在满足A[L]~A[R]的校验值在不超过T的前提下,最大能取到多少,这样我们同时也就确定了R是多少
    求长度为N的一段的校验值需要排序配对,复杂度为O(NlogN)。当校验值上限T比较小时,如果在整个L~N的区间上二分右端点R,二分第一步就要先检验(N-L)/2最终右端点R却可能只扩展了一
    点,和上一题童同样的锅,浪费了很多时间。所以我们采用倍增
    用与上一题类似的倍增过程
    1.p = 1, R=L
    2.求出[L, R+p]的校验值,若校验值<=T,则R+=p,p*=2,否则p/=2
    3.重复上一步,直到p值变为0,此时R即为我们要求的右端点。
    然后我们可以不断进行上述操作直到扩展完整个A
    上面的过程至多循环O(logN)次,每次循环对长为O(R-L)的一段进行排序,完成整个题目的求解累计扩展长度为N,所以总体复杂度为O(Nlog^2N)。
    虽然与二分相比,总复杂度仍是O(Nlog^2N)没有改变,但是在一些特殊情况下,远比二分跑得快。

    ST表

    在RMQ问题(区间最值问题)中,著名的ST算法就是倍增的产物。给定一个长度为N的数列,ST算法能在O(nlong)时间的预处理后,以O(1)的时间复杂度在线回答“数列A中下标在l~r之间
    的数的最大值是多少”这样的区间最值问题(当然,线段树也能qwq(逃)
    一个序列的子区间个数为O(N^2)个。根据倍增思想,现在规模为O(N^2)的区间中选择一些2的整次幂的位置代表值
    设f[i,j]表示数列A中下标在子区间[i,i+2^j-1]里的最大值,也就是从i开始的2^j个数的最大值,。递推的边界为f[i,0] = A[i],即数列在子区间f[i,i]里的最大值中较大的一个
    递推时,我们将子区间长度成倍增长,f[i,j] = max(f[i,j - 1], f[i + 2^j, j - 1]),即长度为2^j的区间的最大值是左右两半长度为2^(j-1)的子区间的最大值中较大的一个

     1 void ST_prework() {
     2     for(int i = 1; i <= n; ++i) f[i][0] = a[i];
     3     int t = log(n) / log(2) + 1;
     4     for(int j = 1; j < t; ++j)
     5         for(int i = 1; i <= n - (1 << j) + 1; ++i)
     6             f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
     7 }
     8 /*
     9 我们之所以要先枚举j,再枚举i,我们想我们要完成全部状态的更新,如果我们单独把每一个i更新完成,那么对于更新这个i时所需要的其余区间的信息并未得到更新,
    10 会导致情况的遗漏(大概和floyed的锅有点像?我也不是很确定。错了别打我
    11 */

     

    当询问任意区间[l,r]的最值时,我们先计算出一个k,满足2^k<r-l+1<=2^(k+1),也就是使2的k次幂小于区间长度的前提下最大的k,那么“从l开始的2^k个数”和“以r结尾的2^k个数”,这两
    段一定覆盖了整个区间[l,r],这两段的值分别是f[l,k]和f[r-2^k+1,k]的最值,二者中较大的就是整个区间的最值。

    1 int ST_query(int l, int r) {
    2     int k = log(r - l + 1) / log(2);
    3     return max(f[l][k], f[r - (1 << k) + 1][k]);
    4 }

     最后说说ST表与线段树,线段树在建树时的复杂度为O(nlogn),查询为O(logn),在查询时复杂度高于ST表,毕竟ST表示O(1)为所欲为,但是线段树优秀就优秀在支持在线修改,复杂度O(logn)

    而ST表,在线修改?反正各有优劣吧

    单调队列

    给定一个长度为N的整数序列(可能有负数),从中找出一段长度不超过M的连续子序列,使得子序列中所有数的和最大。N,M <= 3*10^5
    计算“区间和”问题,一般转化为“两个前缀和相减”的形式进行求解。我们先算出s[i]表示序列里前i项的和,则连续子序列[L,R]中的和就等于s[R]-s[L-1]。所以子问题转化为:
    找出两个位值x,y,使s[y] - s[x]最大并且y - x <= M。
    首先我们枚举右端点i,当i固定时,问题就变为:找到一个左端点j,其中j∈[i - m, i - 1]并且s[j]最小

    比较一下任意两个位置j和k,如果k < j < i并且s[k] >= s[j],那么对于所有大于i的右端点,k永远不会是最优选择,因为不但s[k]不小于s[j],并且j离i更近,长度更容易不超过M
    也就是说j的生存能力比k更强。所以当j出现后,k就是一个无用的位置了

    这告诉我们:可能成为最优选择的策略集合的一定是一个“下标位置递增,对应的前缀和s的值也递增”的序列,我们可以用一个队列保存这个序列。随着右端点的变化从前向后扫描,我们对
    每个i执行以下三个步骤:
    1.判断队头决策与i的距离是否超过M的范围,若超出则出队
    2.此时队头就是右端点为i时,左端点为j时的最佳选择
    3.不断删除队尾决策,直到队尾的S值小于s[i]。然后把i作为一个新的决策入队

    1 int l = 1, r = 1;
    2 q[1] = 0;
    3 for(int i = 1; i <= n; ++i) {
    4     while(l <= r && q[l] && q[l] < i - m) l++;
    5     ans = max(ans, s[i] - s[q[l]]);
    6     while(l <= r && sum[q[r]] >= s[i]) r--;
    7     q[++r] = i;
    8 }

     

     

     

  • 相关阅读:
    HDU 1847 Good Luck in CET-4 Everybody!(博弈、找规律)
    HDU 1564 Play a game(博弈、找规律)
    HDU 2188 悼念512汶川大地震遇难同胞——选拔志愿者(巴什博弈)
    HDU 2516 取石子游戏(斐波那契博弈)
    HDU 1517 A Multiplication Game(博弈)
    HDU
    POJ-1182 食物链(并查集)
    POJ-1915 Knight Moves (BFS)
    python模块详解 time与date time
    python模块介绍和 import本质
  • 原文地址:https://www.cnblogs.com/ywjblog/p/9056031.html
Copyright © 2011-2022 走看看