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

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]加上 k k k,所以我们建立差分数组 d i f dif dif,针对每次计划:
d i f [ l ] + = k ; d i f [ r + 1 ] − = k dif[l]+=k;dif[r+1]-=k dif[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 ) = x f(x) = x f(x)=x差分后 d i f ( x ) = f ( x ) − f ( x − 1 ) = 1 dif(x) = f(x) - f(x-1) = 1 dif(x)=f(x)f(x1)=1变成常数序列,再差分一下就可以搞了,经历了两次差分,我们对于差分数组求两次前缀和就可以了,但是要知道,多次差分的右端点处理比较麻烦,每求一次前缀和就要再次抵消一次本次修改的影响.但是这个题目每次修改的影响范围是 [ i , n ] [i, n] [i,n]无需考虑右端点.所以,最后做法是:针对每次在起点i的修改我们都执行: d i f [ i ] + = 1 ; dif[i]+=1; dif[i]+=1;然后 d i f dif dif求两遍前缀和就得到了原数组.
标程代码:

#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 + 1 r-l+1 rl+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.按照重量再次由大到小排序,然后处理出闪耀值前缀最大值,即 m a x x [ i ] maxx[i] maxx[i]为前i个数的闪耀值最大值.
3.针对每次查询a,二分 m a x x maxx maxx ( m a x x maxx maxx数组显然是非递减的) 找到第一个大于等于a的标号,则这个宝石的重量值就是最小重量值(依据贪心思想,比它靠前的 m a x x [ i ] maxx[i] maxx[i]无法满足要求,比它靠后的重量值比它大),然后得出所求最小重量值 a n s w answ answ后对 a n s w answ answ的vector进行二分,找到这个重量值下又多少闪耀值大于等于a的,得出数量.
最后的时间复杂度,每次查询 O ( l o g n ) O(logn) O(logn)预处理是 O ( n l o g n ) O(nlogn) O(nlogn)所以最后时间复杂度为 O ( ( m + n ) l o g n ) 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)进行分析,发现是首项为 6 k \frac{6}{k} k6公差为 3 k \frac{3}{k} k3的等差数列,于是有通向公式: g ( x ) = 3 ( n + 1 ) k g(x)=\frac{3(n+1)}{k} g(x)=k3(n+1),计算 h ( x ) = f ( x ) ∗ g ( x ) + a = 3 k ( n 2 + n ) 2 + a h(x)=f(x)*g(x)+a=\frac{3k(n^2+n)}{2}+a h(x)=f(x)g(x)+a=23k(n2+n)+a,因为训练营的成绩值是所有人成绩值累加值,故拆成两部分 h 1 ( x ) = 3 k ( n 2 + n ) 2 h_1(x)=\frac{3k(n^2+n)}{2} h1(x)=23k(n2+n) h 2 ( x ) = a h_2(x)=a h2(x)=a分别累加对成绩无影响(加法交换律)
h 2 ( x ) h_2(x) h2(x)是常数列可以直接用差分处理.对 h 1 ( x ) h_1(x) h1(x)进行两次差分, h 1 ′ ( x ) = h 1 ( x ) − h 1 ( x − 1 ) h_1^{'}(x)=h_1(x)-h_1(x-1) h1(x)=h1(x)h1(x1)紧接着 h 1 ′ ′ ( x ) = h 1 ′ ( x ) − h 1 ′ ( x − 1 ) = 3 k h_1^{''}(x)=h_1^{'}(x)-h_1^{'}(x-1)=3k h1(x)=h1(x)h1(x1)=3k为常数列,且每个人的影响范围是 [ i , n ] [i,n] [i,n]不用考虑右端点.我们分别计算好 h 1 ( x ) h_1(x) h1(x) h 2 ( x ) h_2(x) h2(x)在n天的累计值,然后累加起来的 s u m sum sum数组就是每天训练营的成绩值,然后 s u m sum sum数组排序,二分就可以得出答案.
修改一共为 O ( m ) O(m) O(m)后续处理是 O ( n l o g n ) O(nlogn) O(nlogn),每次查询都是 O ( l o g n ) O(logn) O(logn)所以最终时间复杂度为 O ( m + ( n + q ) l o g n ) 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;
}
树上选点是蓝桥杯Java题目中的一种类型,通常需要在给定的树结构中选择一个或多个节点作为目标节点,并进行相应的操作。下面是一个简单的树上选点蓝桥Java题解的示例: 题目描述: 给定一棵有N个节点的树,每个节点上都有一个非负整数值。现在需要选择一些节点,使得选择的节点的值之和最大,且所选节点不能相邻(即选了一个节点,则其父节点和子节点都不能选)。请编写一个程序,计算出最大的节点值之和。 解题思路: 这是一个典型的动态规划问题。我们可以定义一个数组dp,其中dp[i]表示以第i个节点为根节点的子树中所选节点的最大值之和。对于每个节点i,有两种情况: 1. 选择节点i:则其子节点都不能选,所以dp[i] = val[i] + dp[grandchild1] + dp[grandchild2] + ... 2. 不选择节点i:则其子节点可以选择或不选择,所以dp[i] = max(dp[child1], dp[child2], ...) 根据以上思路,我们可以使用递归或者迭代的方式来计算dp数组。最终,所求的最大值即为dp,其中1表示根节点。 代码示例: ```java public class TreeSelectPoint { public static void main(String[] args) { int[] values = {0, 1, 2, 3, 4, 5}; // 节点值数组,下标从1开始 int[][] edges = {{1, 2}, {1, 3}, {2, 4}, {2, 5}}; // 树的边关系数组 int n = values.length - 1; // 节点个数 int[] dp = new int[n + 1]; // 动态规划数组 // 构建树的邻接表 List<List<Integer>> adjacencyList = new ArrayList<>(); for (int i = 0; i <= n; i++) { adjacencyList.add(new ArrayList<>()); } for (int[] edge : edges) { int u = edge[0]; int v = edge[1]; adjacencyList.get(u).add(v); adjacencyList.get(v).add(u); } dfs(1, -1, values, adjacencyList, dp); // 从根节点开始进行深度优先搜索 System.out.println(dp[1]); // 输出最大节点值之和 } private static void dfs(int cur, int parent, int[] values, List<List<Integer>> adjacencyList, int[] dp) { dp[cur] = values[cur]; // 初始化当前节点的dp值为节点值 for (int child : adjacencyList.get(cur)) { if (child != parent) { // 避免重复访问父节点 dfs(child, cur, values, adjacencyList, dp); dp[cur] += dp[child]; // 更新当前节点的dp值 } } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值