算法题目技巧总结

有关平均值的一个技巧

判断一个区间an am的平均值avg是否大于一个数b,可等价为判断前缀和数组中smsn1的大小关系

证明:
avg=n i  ma[i]mn+1

avgb=n i  ma[i]mn+1b=n i  ma[i](mn+1)bmn+1

因为mn+1>0, 所以avgb的大小关系等价为n i  ma[i](mn+1)b与0的大小关系,即每一个a[i]减去b之后的和与0的关系
区间求和如果采用前缀和的方式,问题又可等价为smsn1与0的关系,即smsn1的大小关系

应用实例
最佳牛围栏一题中使用该技巧实现了算法的关键优化

O(n)预处理以2为底的对数

log2[1] = 0; // 循环会将log2[1]计算为1
for (int i = 2; i <= n; ++ i) log2[i] = log2[i >> 1] + 1;

正确性证明:对于log2[x],x只有两种可能

  1. x=2k,此时log2x=log22k=log2(2k12)=log22k1+1=log2(x>>1)+1

  2. x=2k+c,此时0<c<2k, log2x=log2(2k+c)实际结果应该位于kk+1之间,即k.xxx,因为我们计算对数时实际是对结果下取整,所以log2x=k

    接下来证明log2x2+1=k

    2k2=2k10<c<2k,所以0<c2<2k1

    根据log2(2k+c)=k(0<c<2k)可得,log2(2k1+d)=k1,(0<d<2k1), 所以log2(x2)=k1, 所以证得log2(x2)+1=k

综上所述,log2[x]=log2[x>>1]+1

质数$约数转换计算方向优化时间复杂度

约数: 轻拍牛头
质数: 阶乘分解
从数据算约数,质因数分解复杂度较高
约数和质因子筛会更快

相同数字组成的数据形式的转化

所谓相同的数字,是指像11111,22222,...这种aaaa...形式的数字
我们以8888为例子,8888=81111=899999=810419
所以 aaaa...=a10x19
同时x恰好就是原数据的位数
这个技巧在最幸运的数字一题中出现

mod m的情况下求最小正整数

(x % m + m) % m
应用实例见扩展欧几里得多解时取最小正整数

隔板法

问题类型
a1+a2+a3+ ... +ak=n
ai>=1的情况下询问一共有多少组解(等价于正整数解)。不同顺序并不相同
eg:k=3,n=4时,共有3组解

解决方法

10进制转k进制精简写法

for (int i = 0; x; x /= k, ++ i)
  s[i] += x % k;

正向求解与反向验证

有些时候,验证问题要比求解问题简单得多

北极通讯网络一题就是这个结论的最好证明
此问题可以简述为:在条件k的约束下,求解d的最优解
正向求解的思路是,构造出最小生成树,使用那k台卫星设备去掉较大的几条边,剩余边中最大的即为答案,但是由点确定边时,选择不同位置的点所确定的边也是不同的,这点是很难确定的
反向求解的思路是,假设已经确定了d的值,在图中那些<=d的边可以存在,反之不能,最终构成了多个连通块,连通块之间的通信则需要使用卫星设备,有几个连通块就需要使用几个卫星设备,通过与k进行比较,即可确定该d值是否合法
在“在条件k的约束下,求解d的最优解”这种题型下,一种解决思路是枚举d值,采用k对d进行评判,从而找到答案。利用k对d进行评判就是验证问题的过程

等式与不等式转化

a==b<=>a<=b && a>=b
目前已知的应用场景是差分约束,题目中给定的是等于关系,但要转换为不等关系

最大值和次大值

求一些数据中的最大值和次大值

const int INF = 0x3f3f3f3f;

int a[] = {1, 2, 3, 4};
int max1 = -INF, max2 = -INF; // 依次为最大值,次大值

for (int i = 0; i < 4; ++ i)
{
    if (a[i] > max1) max2 = max1, max1 = a[i];
    else if (a[i] != max1 && a[i] > max2) max2 = a[i];
}

遍历一维空间同时获取对应二维空间位置

实质上把一维空间当作二维空间进行for循环,同时使用一个单独的下标枚举一维空间

#include <iostream>

using namespace std;

int main() {
    int nums[81];
    for (int i = 0; i < 81; ++i) {
        nums[i] = i % 9;
        cout << nums[i];
    }

    for (int i = 0, k = 0; i < 9; ++i) {
        for (int j = 0; j < 9; ++j, ++k) cout << nums[k];
        cout << endl;
    }

    return 0;
}

距离计算

在一连续区间中,计算两个元素之间的距离有两种方案:

  1. 采用计数器Counter
  2. 通过下标运算

方法1代码实现细节上要明显多于方法2,即更容易出错,至于优点暂时还没遇到

构造10进制回文数

结论:

  1. 对于[10i,10i+1)范围内的每个数(长度为i+1),将末尾位的其余位取反拼接到原数末尾,可以获得长度为2i+1的10进制回文数
  2. 对于[10i,10i+1)范围内的每个数(长度为i+1),将所有位取反拼接到原数末尾,可以获得长度为2i+2的10进制回文数

代码实现

void get_palindrome_number() {
    vector<int> res;
    // 每次获取[10^start, 10^start - 1)
    for (int start = 1; start <= 10; start *= 10) {
        // 除末尾位其余位取反拼接到原数末尾获得长度为2*len(start)+1的回文数
        for (int i = start; i <= start * 10 - 1; ++i) {
            int num = i;
            for (int j = i / 10; j; j /= 10) // 起始先/10将末尾位删除
                num = num * 10 + j % 10;
            res.push_back(num);
        }

        // 所有位取反拼接到原数末尾获得长度为2*len(start)+2的回文数
        for (int i = start; i <= start * 10 - 1; ++i) {
            int num = i;
            for (int j = i; j; j /= 10)
                num = num * 10 + j % 10;
            res.push_back(num);
        }
    }

    for (int num : res) 
        cout << num << ' ';
    cout << endl;
}

枚举给定长度区间

for (int start = 1; start + len - 1 <= n; ++start) { // 枚举起点
    int end = start + len - 1; // 计算终点
}
posted @   0x7F  阅读(132)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示