b本篇为方便博主笨比时及时进行算法知识点的一个一个快速回顾

赛前提醒

  • 能用long long和double就用
  • 稳定心态,减少失误,能得就得,至少能把自己能力范围内的分100%拿到
  • 先通看一遍,有个整体把握.
  • 三思而后行(思考对了才好实现),一定要花足够多的时间去读题和构思(花多点时间读题思考肯定比在一个错误的思路上进行要好的多!多一分钟好的思考也许能减少五分钟的码量)
    • 仔细读题,确保每个字眼都读到读懂
      • 数据范围及分布
      • 极端/边界情况
    • 构思代码
      • 多用稿纸辅助思考,推算思路
      • 不要尝试用不熟练的算法,就靠已学尽力做就好
    • 实现代码
      • 一遍实现一遍思考极端,边界等情况(极端小数据 ,边界数据等)思路是否仍正确
    • 检查
      • 低级错误(变量名,数组清空,初始化赋值,符号)
      • 提交前多多测试几个数据,自己捏也好,generate一些也好
    • 有时间的话,对于没有把握的题,不要吝啬去蒙去猜结论,肯定是稳赚不赔的
      • 先暴力搜索+分段(先处理小的,对大的单独优化),再优化(剪枝,记忆化)
      • 加上贪心
      • 打表
      • 找规律
  • 除非还能有把握,不然最后10分钟就全面检查(中间就注意保存),看看好还能不能救(命名,数组大小等)

小知识点

  • 传入常数时注意使用lld强转
  • 一秒认定为2e8
  • 堆上256MB最多可开5e7个int最好不超1e7(所以开大数组时再慎用lld)
  • erase会返回所删除元素的下一个元素的迭代器,赋值即可

小技巧

  • 在main()里面用solve()函数,方便运行至中间就结束
  • 学会多去使用空间记录来简化代码量(本来也就用不完)
  • 对于二维的状态压缩,可以先用一维的去存储,在处理时再转化为二维
  • 若有多次求取而有可能出现重复,则使用记忆化数组记下第一次求的值

铸币小错

基础算法

贪心

只看目前何种选择最好就去选(局部的,对于全局来说不一定选择这个就最好,可能现在差点会对全局更好)

证明其正确性即证明局部最优解就是全局最优解(局部最好就对全局最好,一般要独立的或能共同好的)

差分(不同于用树状数组,维护的是变化数组,并非差分)

当需要多次修改区间O(1)但仅少量查询O(n)时可用

二维前缀和

注意维护好边界情况,看好是否要减去或是加上(写成区块去看)

向右下加和,块+块-重叠+单点

求取 块-(块+块-重叠)+(边界)

二维差分

差分<->原数组<->前缀和三者之间都可运算互化

二维差分定义式 b[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]

修改时注意b[x2+1][y2+1]处也要+1

二进制枚举

表示用一个二进制数(虽然短,但其实能对应很多种情况,所以也是独特的)来表示一个子集(相对于总体集合,每个元素选与不选),从而共可能有2的n次方种情况

即为状态压缩,用一个二进制数特别地表示出一个状态

单调队列

元素入队前队首会破坏区间性队尾会破坏单调性的元素去掉(队列保存下标即可,调用原数组读取值,否则要用pair)

单调栈

每个元素最多出入一次

元素入栈前栈顶会破坏单调性的元素去掉

数论

gcd*lcm=a*b

组合数思想

互补性质189b3a44f8bcf6b0e687730f596d413d

杨辉三角

7e32652740f3f8403d4fea252c15d9ed

可用于线性递推二项式系数(先乘后除)69c41febe0e1fd9d41106447ed434f31

直观解释

2536917b2042b0806ac454700cb38442

Cmn的写法

1
2
3
4
5
lld C(lld m,lld n)
{
if(n==m||m==0) ret 1;//边界情况
ret (C(m-1,n-1)+C(m,n-1));
}

字符串

求回文串(接近线性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;

#define ull unsigned long long
const int N = 2000010, P = 131;

char s[N];
ull h1[N], h2[N], p[N];

ull get(ull h[], ull l, ull r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
int cnt = 0;
while (scanf("%s", s + 1) && strcmp(s + 1, "END"))
{
int n = strlen(s + 1) * 2;
for (int i = n; i; i -= 2)
{
s[i] = s[i / 2];
s[i - 1] = 'z' + 1;
}
s[ ++ n] = 'z' + 1;
p[0] = 1;
for (int i = 1, j = n; i <= n; i ++ , j -- )
{
h1[i] = h1[i - 1] * P + s[i] - 'a' + 1;
h2[i] = h2[i - 1] * P + s[j] - 'a' + 1;
p[i] = p[i - 1] * P;
}
ull ans = 1;
for (int i = 1; i <= n; i ++ )
{
ull r = min(i - 1, n - i);
if (ans >= r || get(h1, i - ans, i - 1) != get(h2, n - (i + ans) + 1, n - i)) continue;
while (ans <= r && get(h1, i - ans, i - 1) == get(h2, n - (i + ans) + 1, n - i)) ans ++ ;
ans -- ;
}
printf("Case %d: %d\n", ++ cnt, ans);
}
return 0;
}

dp

阶段:对应若干个子问题

状态表示f[i][j]:子问题的特征

决策:状态转移方程

状态设置要求

要设置成互无影响的,不重不漏的状态,能划分而确定好局部与全局的关系(部分好能推得全局好,否则换一种状态表示)

此后,别以为可能会互相囊括冲突,其实因为有维度i,j的限制,每个值都是要看作两两独立的(只是讲求取时不会互相有囊括冲突影响)

满足三个条件:最优子结构,无后效性和子问题重叠。

最优子结构

具有最优子结构也可能是适合用贪心的方法求解。

注意要确保我们考察了最优解中用到的所有子问题

  1. 证明问题最优解的第一个组成部分是做出一个选择;
  2. 对于一个给定问题,在其可能的第一步选择中,假定你已经知道哪种选择才会得到最优解(假设要是求得的就会是最优(但其实现在还没求得),那么也一定是由求得的子问题的最优解得到的(即肯定会是由最优继承,不可能有漏解或非最优继承影响)),现在并不用关心这种选择具体是如何得到的.
  3. 给定可获得的最优解的选择后,确定这次选择会产生哪些子问题,以及如何最好地刻画子问题空间
  4. 证明在这一种决策下,作为构成原问题最优解的组成部分,每个子问题的解就是它本身的最优解。方法是反证法,考虑加入某个子问题的解不是其自身的最优解,那么就可以从原问题的解中用该子问题的最优解替换掉当前的非最优解,从而得到原问题的一个更优的解,从而与原问题最优解的假设矛盾。

要保持子问题空间尽量简单,只在必要时扩展。

最优子结构的不同体现在两个方面:

  1. 原问题的最优解中涉及多少个子问题;
  2. 确定最优解使用哪些子问题时,需要考察多少种选择。

子问题图中每个定点对应一个子问题,而需要考察的选择对应关联至子问题顶点的边。

无后效性

已经求解的子问题,不会再受到后续决策的影响。

子问题重叠

如果有大量的重叠子问题,可以记忆化避免重复求解(在当前阶段解决该阶段的总体的子问题需要解决若干个子问题的子问题,而部分子问题的子问题在之前的阶段已经求过了)相同的子问题,从而提升效率(方便由先前状态得出的最优值,判断性继承)。