青岛大学软件梦工厂蓝桥讲课_前缀和与差分题解

本篇博客详细解析了zx学长的多个经典算法题目,涵盖了差分数组、二维差分、等差数列、贪心算法、排序、二分查找、前缀和等核心算法概念。通过具体题目的实现代码,深入浅出地讲解了算法的设计思路和实现细节。
A.zx学长的施工队1

解题思路在视频中已给出,
标程代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 1e5+5;
int a[N], sum[N];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++)
		sum[i] = sum[i-1] + a[i];
    int m;
    cin >> m;
    while(m--)
    {
        int l, r;
		bool flag = 0;
        scanf("%d%d", &l, &r);
		if (sum[r] - sum[l-1] == r - l + 1) puts("0");
		else puts("1");
    }
    return 0;
}
B.zx学长的施工队2

解题思路在视频中已给出,
标程代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 1e5+5;
int a[N], dif[N];
int main()
{
    int n, m;
    while(cin >> n >> m)
    {
        memset(dif, 0, sizeof(dif));
        while(m--)
        {
            int l, r;
            scanf("%d%d", &l, &r);
            dif[l]++;
            dif[r+1]--;
        }
        bool flag = 0;
        for (int i = 1; i <= n; i++)
        {
            a[i] = a[i-1] + dif[i];
            if (a[i] == 0)
            {
                flag = 1;
                break;
            }
        }
        if (flag) puts("0");
        else puts("1");
    }
    return 0;
}
C.zx学长的口罩支援计划

这个题是一道非常裸的差分题,每次计划都是在区间[l,r][l, r][l,r]加上kkk,所以我们建立差分数组difdifdif,针对每次计划:
dif[l]+=k;dif[r+1]−=kdif[l]+=k;dif[r+1]-=kdif[l]+=k;dif[r+1]=k
最后求一边前缀和,即可得出答案.
标程代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 1e5+5;
ll a[N], dif[N];
int main()
{
    int n, m;
    cin >> n >> m;
    while(m--)
    {
        int l, r;
        ll k;
        scanf("%d%d%lld", &l, &r, &k);
        dif[l] += k;
        dif[r+1] -= k;
    }
    for (int i = 1; i <= n; i++)
        a[i] = a[i-1] + dif[i];
    bool flag = 1;
    for (int i = 1; i <= n; i++)
    {
        if (flag) flag = 0;
        else printf(" ");
        printf("%lld", a[i]);
    }
    puts("");
    return 0;
}
D.zx学长的差分教学

每次修改都是从一个起点开始,向后加一个首项为1,公差为1的等差序列:f(x)=xf(x) = xf(x)=x差分后dif(x)=f(x)−f(x−1)=1dif(x) = f(x) - f(x-1) = 1dif(x)=f(x)f(x1)=1变成常数序列,再差分一下就可以搞了,经历了两次差分,我们对于差分数组求两次前缀和就可以了,但是要知道,多次差分的右端点处理比较麻烦,每求一次前缀和就要再次抵消一次本次修改的影响.但是这个题目每次修改的影响范围是[i,n][i, n][i,n]无需考虑右端点.所以,最后做法是:针对每次在起点i的修改我们都执行:dif[i]+=1;dif[i]+=1;dif[i]+=1;然后difdifdif求两遍前缀和就得到了原数组.
标程代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 1e5+5;
ll a[N], dif[N];
int main()
{
    int n, m;
    cin >> n >> m;
    while(m--)
    {
        int l;
        scanf("%d", &l);
        dif[l]++;
    }
    for (int i = 1; i <= n; i++)
        a[i] = a[i-1] + dif[i];
    for (int i = 1; i <= n; i++)
        a[i] += a[i-1];
    bool flag = 1;
    for (int i = 1; i <= n; i++)
    {
        if (flag) flag = 0;
        else printf(" ");
        printf("%lld", a[i]);
    }
    puts("");
    return 0;
}
E.zx学长的幸运线段

这个题并非传统的差分,就拿样例来说,它其实是这么一种情况
在这里插入图片描述
这时候第二次查询[1,3][1, 3][1,3]的时候,显然没有其中的[2,3][2, 3][2,3]没有覆盖到,但是如果按照传统差分,就会被判为完全覆盖,所以我们可以看出:一个点被覆盖和一段线段被覆盖是不一样的.所以我们的做法是边化点,让每个点代替它左边的区间段,例如3号点代表的是[2,3][2, 3][2,3]这个区间,这样我们修改时考虑:比如说样例中的第三个线段[3,5][3, 5][3,5]它覆盖的其实是4和5两个点代表的区间段,所以我们不修改[3,5][3, 5][3,5],而是修改[4,5][4, 5][4,5],这样每次查询或者修改[l,r][l, r][l,r]我们都变换成[l+1,r][l+1, r][l+1,r],然后正常差分即可.差分方法:每次修改区间,我们都让这个区间的数值加一,这样最后求一边前缀和,得出的是每个点的覆盖次数,然后我们一遍变换,将所有覆盖次数大于0的(被覆盖过的)都变成1,然后再求前缀和,这样每次查询区间[l,r][l, r][l,r]只要它的区间元素和为r−l+1r-l+1rl+1我们就认定是全部被覆盖(类比A题).
标程代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 1e6+5;
int a[N], dif[N];
int main()
{
    int n, m;
    cin >> m;
    while(m--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        dif[l+1]++;
        dif[r+1]--;
    }
    for (int i = 1; i < N; i++)
        a[i] = a[i-1] + dif[i];
    for (int i = 1; i < N; i++)
        if (a[i]) a[i] = 1;
    for (int i = 1; i < N; i++)
        a[i] += a[i-1];
    int q;
    cin >> q;
    while(q--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        if (a[r] - a[l] == r - l)
            puts("GoodLuck!");
        else puts("No!");
    }
    return 0;
}
F.zx学长的抓捕行动

虽然题目比较长,但是是比较裸的的二维差分,我们每次按照监控,都把这个监控所能探测的子矩阵中元素都加一,然后求一边前缀和,就是这个矩阵每个元素被覆盖的次数,然后我们再次求前缀和,如果小偷作案的所有矩形中的元素的和都是0,则说明没有监控被覆盖到,就抓不到,否则就会被捉到.
标程代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 2e3+5;
int a[N][N], dif[N][N];
int main()
{
    int n, m;
    while(cin >> n >> m)
    {
        memset(dif, 0, sizeof(dif));
        int q;
        cin >> q;
        while(q--)
        {
            int x1, y1, x2, y2;
            scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
            dif[x1][y1]++;
            dif[x2+1][y1]--;
            dif[x1][y2+1]--;
            dif[x2+1][y2+1]++;
        }
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + dif[i][j];
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1];
        int p;
        cin >> p;
        bool flag = 0;
        while(p--)
        {
            int x1, y1, x2, y2;
            scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
            if (a[x2][y2] - a[x1-1][y2] - a[x2][y1-1] + a[x1-1][y1-1])
            {
                flag = 1;
				continue;
            }
        }
        if (flag) puts("YES");
        else puts("NO");
    }
    return 0;
}
G.zx学长的QDU合作

较为简单的题,正常差分,最后比较一下值是不是大于了上限,如果大于上限,就赋值为上限,
标程代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 1e5+5;
int a[N], b[N], dif[N];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++)
        scanf("%d", &b[i]);
    int m;
    cin >> m;
    while(m--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        dif[l]++;
        dif[r+1]--;
    }
    for (int i = 1; i <= n; i++)
        dif[i] += dif[i-1], a[i] += dif[i];
    for (int i = 1; i <= n; i++)
        if (a[i] > b[i]) a[i] = b[i];
    bool flag = 1;
    for (int i = 1; i <= n; i++)
    {
        if (flag) flag = 0;
        else printf(" ");
        printf("%d", a[i]);
    }
    puts("");
    return 0;
}
H.zx学长的蓝桥原题

解题思路在视频中已给出,
标程代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 1e5+5;
ll a[N], col[N];
int main()
{
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    for (int i = 1; i <= n; i++)
    {
        a[i] += a[i-1];
        col[a[i]%k]++;
    }
    ll cnt = 0;
    for (int i = 0; i < k; i++)
        cnt += col[i] * (col[i] - 1) / 2;
    cnt += col[0];
    printf("%lld\n", cnt);
    return 0;
}
I.zx学长的宝石探险

1.先按照闪耀值由大到小排序,每个重量值建立一个vector,将每个闪耀值压入对应重量值的vector中.
2.按照重量再次由大到小排序,然后处理出闪耀值前缀最大值,即maxx[i]maxx[i]maxx[i]为前i个数的闪耀值最大值.
3.针对每次查询a,二分maxxmaxxmaxx (maxxmaxxmaxx数组显然是非递减的) 找到第一个大于等于a的标号,则这个宝石的重量值就是最小重量值(依据贪心思想,比它靠前的maxx[i]maxx[i]maxx[i]无法满足要求,比它靠后的重量值比它大),然后得出所求最小重量值answanswansw后对answanswansw的vector进行二分,找到这个重量值下又多少闪耀值大于等于a的,得出数量.
最后的时间复杂度,每次查询O(logn)O(logn)O(logn)预处理是O(nlogn)O(nlogn)O(nlogn)所以最后时间复杂度为O((m+n)logn)O((m+n)logn )O((m+n)logn)
标程代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 1e6+5;
vector<int> w[N];
struct Node
{
    int w, s;
}a[N];
int maxx[N];
bool cmp1(const Node &a, const Node &b) {return a.s < b.s;}
bool cmp2(const Node &a, const Node &b) {return a.w < b.w;}
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i].s);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i].w);
    sort(a+1, a+n+1, cmp1);
    for (int i = 1; i <= n; i++)
        w[a[i].w].push_back(a[i].s);
    sort(a+1, a+n+1, cmp2);
    for (int i = 1; i <= n; i++)
        maxx[i] = max(maxx[i-1], a[i].s);
    int q;
    cin >> q;
    while(q--)
    {
        int x;
        scanf("%d", &x);
        int pos = lower_bound(maxx+1, maxx+n+1, x) - maxx;
        if (pos > n)
        {
            puts("-1");
            continue;
        }
        int answ = a[pos].w;
        int l = 0, r = w[answ].size();
        while(l <= r)
        {
            int mid = (l + r) >> 1;
            if (w[answ][mid] >= x) r = mid-1;
            else l = mid+1;
        }
        int ansn = w[answ].size() - l;
        printf("%d %d\n", answ, ansn);
    }
    return 0;
}
J.zx学长的ap训练营

对原始g(x)g(x)g(x)进行分析,发现是首项为6k\frac{6}{k}k6公差为3k\frac{3}{k}k3的等差数列,于是有通向公式: g(x)=3(n+1)kg(x)=\frac{3(n+1)}{k}g(x)=k3(n+1),计算h(x)=f(x)∗g(x)+a=3k(n2+n)2+ah(x)=f(x)*g(x)+a=\frac{3k(n^2+n)}{2}+ah(x)=f(x)g(x)+a=23k(n2+n)+a,因为训练营的成绩值是所有人成绩值累加值,故拆成两部分h1(x)=3k(n2+n)2h_1(x)=\frac{3k(n^2+n)}{2}h1(x)=23k(n2+n)h2(x)=ah_2(x)=ah2(x)=a分别累加对成绩无影响(加法交换律)
h2(x)h_2(x)h2(x)是常数列可以直接用差分处理.对h1(x)h_1(x)h1(x)进行两次差分,h1′(x)=h1(x)−h1(x−1)h_1^{'}(x)=h_1(x)-h_1(x-1)h1(x)=h1(x)h1(x1)紧接着h1′′(x)=h1′(x)−h1′(x−1)=3kh_1^{''}(x)=h_1^{'}(x)-h_1^{'}(x-1)=3kh1(x)=h1(x)h1(x1)=3k为常数列,且每个人的影响范围是[i,n][i,n][i,n]不用考虑右端点.我们分别计算好h1(x)h_1(x)h1(x)h2(x)h_2(x)h2(x)在n天的累计值,然后累加起来的sumsumsum数组就是每天训练营的成绩值,然后sumsumsum数组排序,二分就可以得出答案.
修改一共为O(m)O(m)O(m)后续处理是O(nlogn)O(nlogn)O(nlogn),每次查询都是O(logn)O(logn)O(logn)所以最终时间复杂度为O(m+(n+q)logn)O(m+(n+q)logn)O(m+(n+q)logn)
标称代码:

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
const int N = 1e5+5;
ll sum[N], dif[N];
int main()
{
    int n, m;
    cin >> n >> m;
    while(m--)
    {
        ll l, k, a;
        scanf("%lld%lld%lld", &l, &k, &a);
        sum[l] += a;
        dif[l] += 3 * k;
    }
    for (int i = 1; i <= n; i++)
    {
        sum[i] += sum[i-1];
        dif[i] += dif[i-1];
    }
    for (int i = 1; i <= n; i++) dif[i] += dif[i-1];
    for (int i = 1; i <= n; i++) dif[i] += dif[i-1];
    for (int i = 1; i <= n; i++) sum[i] += dif[i];
    sort(sum+1, sum+n+1);
    int q;
    cin >> q;
    while(q--)
    {
        ll x;
        scanf("%lld", &x);
        int pos = lower_bound(sum+1, sum+n+1, x) - sum;
        printf("%d\n", n - pos + 1);
    }
    return 0;
}
内容概要:本文档是一份针对2025-2026年Java后端大厂面试的高频考点全面梳理,涵盖Java基础、集合框架、并发编程、JVM、Spring全家桶、MySQL、Redis、消息队列、分布式微服务等核心技术模块。内容不仅包括经典概念辨析(如StringStringBuilder区别、HashMap底层结构),还深入源码机制设计原理(如Spring三级缓存解决循环依赖、AOP动态代理实现),并结合实际场景探讨问题排查技术选型(如GC调优、缓存穿透解决方案)。特别强调从“背八股”向源码理解、线上排障和设计权衡的能力转变,体现当前面试趋势的深度化实战化。; 适合人群:具备1-3年工作经验,准备冲击中高级Java岗位的研发人员,尤其适合希望系统提升面试竞争力、深入理解主流技术底层原理的开发者。; 使用场景及目标:①应对大厂Java后端技术面试,掌握高频考点最新趋势;②深入理解核心技术的设计动机实现细节,如ConcurrentHashMap的线程安全机制、分布式ID生成方案对比;③提升实际问题分析解决能力,如Full GC排查、事务失效定位等。; 阅读建议:此资源以面试为导向,兼具广度深度,建议结合自身项目经验进行对照学习,注重理解“为什么”而非仅仅记忆结论,对关键知识点应动手验证(如ThreadLocal内存泄漏实验),并在模拟面试中强化表达逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值