【周记】2024暑期集训第三周

日常刷题记录

凌乱的yyy / 线段覆盖

在这里插入图片描述

算法思路

把时间段想成一段段的线段,在一个数轴上有n条线段,现要选取其中k条线段使得这k条线段两两没有重合部分,问最大的k为多少。
最左边的线段放什么最好?
显然放右端点最靠左的线段最好,从左向右放,右端点越小妨碍越少
其他线段放置按右端点排序,贪心放置线段,即能放就放

代码实现

#include <bits/stdc++.h>
using namespace std;
struct T {
    long long be, en;
}a[1000005];
bool cmp(T a, T b) {
    if (a.en == b.en) {
        return a.be < b.be;
    }
    return a.en < b.en;
}
int t, ans = 1, now;
int main() {
    scanf("%d", &t);
    if (t == 0) {
        return 0;
    }
    for (int i = 0; i < t; i++) {
        scanf("%d%d", &a[i].be, &a[i].en);
    }
    sort(a, a + 1, cmp);
    now = a[0].en;
    for (int i = 1; i < t; i++) {
        if (now <= a[i].be) {
            ans++;
            now = a[i].en;
        }
    }
    printf("%d\n", ans);
    return 0;
}

铺设道路

在这里插入图片描述

算法思路

有一排坑,你每次只能选择一段连续的区域填坑,每次只能减少一层他们的深度,已经填平的坑就不能再被选中。
假设现在有一个坑,但旁边又有一个坑。
你肯定会选择把两个同时减1;
那么小的坑肯定会被大的坑“带着”填掉。
大的坑也会减少a[i]-a[i-1]的深度,可以说是“免费的”;
所以这样贪心是对的;
在这里插入图片描述

代码实现

n=int(input())
d=list(map(int, input().split()))
ans=0
for i in range(1,n):
    if d[i]>d[i-1]:
        ans+=d[i]-d[i-1]
print(ans+d[0])

寻找数组中所有和为s的连续区间

算法思路

使用双指针(滑动窗口)从左到右扫描数组,每次移动指针时计算当前窗口的和:

  1. 初始化
    • 设置左指针 l 和右指针 r 均指向数组的开始位置。
    • 初始化 current_suma[l],表示当前窗口的和。
  2. 窗口扩展与收缩
    • 当前窗口和小于目标值 s
      • 右指针 r 向右移动,扩大窗口,并将 a[r] 加到 current_sum 中。
    • 当前窗口和等于目标值 s
      • 输出当前窗口的左右边界(lr)。
      • 移动左指针 l 向右,收缩窗口,同时从 current_sum 中减去 a[l]。如果 l 超过右指针 r,调整右指针 r 使其与 l 对齐,重新计算 current_sum
    • 当前窗口和大于目标值 s
      • 左指针 l 向右移动,收缩窗口,并从 current_sum 中减去 a[l]。如果 l 超过右指针 r,调整右指针 r 使其与 l 对齐,重新计算 current_sum
  3. 结束条件
    • 当右指针 r 到达数组末尾时,停止扫描,程序结束。

代码实现

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, s;
    cin >> n >> s;
    vector<int> a(n);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    int l = 0, r = 0;
    int current_sum = a[0];

    while (r < n)
    {
        if (current_sum == s)
        {
            cout << l << " " << r << endl;
            current_sum -= a[l++];
            if (l > r && l < n)
            {
                r = l;
                current_sum = a[l];
            }
        }
        else if (current_sum < s)
        {
            if (++r < n)
            {
                current_sum += a[r];
            }
        }
        else
        {
            current_sum -= a[l++];
            if (l > r && l < n)
            {
                r = l;
                current_sum = a[l];
            }
        }
    }
    return 0;
}

找出所有和为m的数对

算法思路

先对数组排序,再利用左右指针对撞,左指针扫描值较小的区域,右指针扫描值较大的区域。每一次计算左右指针所指的两数之和,如果比目标m大,那么左移右指针,如果比目标m小,那么右移左指针。

代码实现

#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<int> a;
    for (int i = 0; i < n; i++)
    {
        int x;
        cin >> x;
        a.push_back(x);
    }
    sort(a.begin(), a.end());
    int l = 0, r = n - 1, ans = 0;
    while (l < r)
    {
        if (a[l] + a[r] == m)
        {
            cout << a[l] << " " << a[r]<< endl;
            break;
        }
        else if (a[l] + a[r] < m)
            l++;
        else
            r--;
    }
}

F - Unique Snowflakes

在这里插入图片描述

题意解析

有T个输入数据,每组输入包含一个n和n个数组成的序列,找出最长的连续的且没有重复元素的子序列。

算法思路

用hash记录下每个元素出现的次数,每访问一个新元素就到hash表中查找,如果判定当前元素重复出现了,那么就记录下当前长度,如果比答案大就更新答案。然后更新滑动窗口的起点。

代码实现

这题用python又是不出所料的超时了😇

T=int(input())
for _ in range(T):
    n=int(input())
    nums=[]
    for i in range(n):
        nums.append(int(input()))
    j=0
    hash={}
    ans=0
    for i in range(len(nums)):
        if nums[i] not in hash:
            hash[nums[i]]=1
        else:
            hash[nums[i]]+=1
        while hash[nums[i]]>1:
            hash[nums[j]]-=1
            j+=1
        ans=max(ans,i-j+1)
    print(ans)

还得是cpp

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        vector<int> a(n);
        unordered_map<int, int> hash;
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
        }
        int j = 0;
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            hash[a[i]]++;
            while (hash[a[i]] > 1) {
                hash[a[j]]--;
                j++;
            }
            ans = max(ans, i - j + 1);
        }
        cout << ans << '\n';
    }
    return 0;
}

Subsequence

在这里插入图片描述

题目解析

找出总和 ≥ \geq s的最短连续子串

算法思路

通过双指针来实现。每次给累加器加上右指针所指向的数,如果长度超过了s,用一个while循环开始向右移动左指针i,每一次更新能达到的最短长度minl = min(minl, j - i + 1); while循环结束则代表当前子串的总和不足s,那么继续向右移动右指针j如此循环往复直到右指针到达序列的末尾为止,然后输出minl如果minl的值没有变化就输出0

代码实现

#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        int N, S;
        cin >> N >> S;
        vector<int> a(N);
        for (int i = 0; i < N; i++)
        {
            cin >> a[i];
        }
        int i = 0, j = 0;
        int s = 0, minl = 100001;
        while (j < N)
        {
            s += a[j];
            while (s >= S)
            {
                minl = min(minl, j - i + 1);
                s -= a[i];
                i++;
            }
            j++;
        }
        if (minl == 100001)
        {
            cout << 0 << endl;
        }
        else
            cout << minl << endl;
    }
    return 0;
}

Tallest Cow

在这里插入图片描述

题目解析

给出N头牛的身高,和R对关系(a[i]b[i]可以相互看见。即他们中间的牛都比他们矮)。已知最高的牛为第I头,身高为H,求每头牛的身高最大可能是多少。
题目要求在给定一些高度约束条件的情况下,计算每头奶牛的最大可能高度。

算法思路

因为我们要是每一头牛的身高尽量高,所以我们初始的时候设每一头牛的身高都为H
因为两头牛x,y之间可以相互看到,所以我们需要把区间[x+1,y−1]内牛的身高都减去1
直接操作的话显然会T掉,所以我们可以用差分来维护,每次将差分数组下标为x+1的位置打上标记,即-1,然后将下标为y的位置打上标记,即+1。这个操作的前提条件得是x<y,所以在操作前要做一次判断,然后决定是否交换。
每次操作前要看看是否已经操作过
当x和y相同时,我们不能重复进行操作,也就是说,我们需要判重。

代码实现

#include <iostream>
using namespace std;
const int MAXN = 1e4 + 5;
int cow[MAXN];
bool c[MAXN][MAXN];
int main()
{
    int N, I, H, R;
    cin >> N >> I >> H >> R;
    for (int i = 1; i <= R; i++)
    {
        int x, y;
        cin >> x >> y;
        if (x > y)
            swap(x, y);
        if (c[x][y])
            continue;
        c[x][y] = 1;
        cow[x + 1]--;
        cow[y]++;
    }
    for (int i = 1; i <= N; i++)
    {
        cow[i] += cow[i - 1];
        cout << cow[i] + H <<endl;
    }
}

[蓝桥杯2024初赛] 回文数组

在这里插入图片描述

算法思路

这题其实只要想办法把前一半的数变成和后一半对称就行了。通过把前一半的数和其与目标的差值存放在一个数组d中,然后通过贪心的方法消消乐,只要有两个连在一起的同号的数,就先尝试操作,直到其中的一个变成0,最后把剩下的不连续的那些数每个进行单独操作,然后统计总共需要操作的次数。

代码实现

n = int(input())
a = list(map(int, input().split()))
d = [a[i] - a[n - i - 1] for i in range(n // 2)]# 初始化差值列表
ans = 0
for i in range(1, len(d)):
    if d[i] > 0 and d[i - 1] > 0:
        x = min(d[i], d[i - 1])
        ans += x
        d[i] -= x
        d[i - 1] -= x
    elif d[i] < 0 and d[i - 1] < 0:
        x = max(d[i], d[i - 1])
        ans -= x
        d[i] -= x
        d[i - 1] -= x
ansd = [abs(x) for x in d]
print(ans + sum(ansd))

机器猫斗恶龙

在这里插入图片描述

算法思路

这题用贪心的思路求解。按照最坏的情况假设每一轮攻击都刚好活到了最后,带着一滴血返回营地。从后往前倒着分析,用ana记录每一轮进攻扣除的血,直到补血,用变量sum_计算补血后能有多少血量,如果补血之后的血量是大于0的,那么意味着这一次回营地补的血足够之后的战斗,所以不需要由初始血量来提供不足的血,于是sum_归零。同理如果补血之后的血量小于0,说明还需要初始的血量来提供这部分血,所以不对sum_进行操作。然后将ans归零继续往前遍历。最后输出-sum_+1就是初始的血量。

代码实现

n = int(input())
arr = [0]+list(map(int, input().split()))
ans = 0
sum_ = 0
for i in range(n, -1, -1):
    if arr[i] < 0:
        ans += arr[i]
    else:
        sum_ += (arr[i] + ans)
        if sum_ > 0:
            sum_ = 0
        ans = 0
print(-sum_ + 1)

学生分组

在这里插入图片描述

算法思路

首先考虑两种极端的情况:所有组的人数在操作后都等于下限;所有组的人数在操作后都等于上限制。计算当前的人数,如果当前的人数超出了两个极端情况,那么是不可能的,直接输出-1。
那么剩下的情况一定是有解的,分别算出超出人数上限的小组 多出的人,我们只对这些人进行移动,和低于人数下限的小组 还差的人。可以先让原本人数就在范围内的小组保持不变,然后将超出人数上限小组中的人分给低于人数下限的小组

  • 如果低于人数下限的小组还缺人,那就从原本人数就在范围内的小组分过去。
  • 如果超出人数上限的小组还多人,那就分给原本人数就在范围内的小组
    总之要操作的次数就是超出人数上限的小组多出的人低于人数下限的小组还差的人中较大的那个。

代码实现

n=int(input())
a = list(map(int, input().split()))
l, r = map(int, input().split())
hi = 0  #超出人数上限的小组多出的人
lo = 0  #低于人数下限的小组还差的人
if sum(a) < l*n or sum(a) > r*n:
    print(-1)
else:
    for num in a:
        if num > r:
            hi += num - r
        elif num < l:
            lo += l - num
    print(max(hi, lo))

连续自然数和

在这里插入图片描述

算法思路

因为是区间求和,所以很容易想到利用前缀和解决。只需要枚举区间的端点。可以看出,右端点不可能超出目标数M的一半,因为右端点一定在左端点右边,如果右端点超出了目标数M的一半,就算左端点移动到右端点之前,和都是超出M的,发现这一点后就可以缩小枚举范围了。在枚举左端点的时候得从右往左枚举,这样子能更快找到答案,优化时间复杂度。

代码实现

#include<bits/stdc++.h>
using namespace std;
int m;
int sum[1000005];
int main()
{
	cin>>m;
	for(int i=1;i<=1000000;i++)
	{
		sum[i]=sum[i-1]+i;
	}
	for(int i=1;i<=m/2+1;i++)
	{
		for(int j=i;j>=1;j--)
		{
			if(sum[i]-sum[j-1]>m)break;//左端点再往左移动必定是超出m的,直接退出
			if(sum[i]-sum[j-1]==m&&i!=j)
			{
				printf("%d %d\n",j,i);
			}
		}
	}
}

宝石串

在这里插入图片描述

算法思路

暴力枚举所有区间,如果区间中红绿宝石个数相同,比较并更新答案。

代码实现

s = input()
n = len(s)
ans = 0
for i in range(n):
    d = 0
    for j in range(i, n):
        if s[j] == 'G':
            d -= 1
        else:
            d += 1
        if d == 0:
            ans = max(j - i + 1, ans)
print(ans)

排列排序

在这里插入图片描述

算法思路

  1. 遍历排列: 从第一个元素开始,检查每个元素是否在正确的位置。如果 a[i] == i,说明当前元素已经在正确的位置,直接跳到下一个元素。如果不在正确的位置,则需要找出一个区间 [i, j],该区间内的所有元素应该排序,使得它们的值变成正确的。
  2. 查找排序区间: 通过扩展区间来找到所需的最小排序区间。maxv 是当前区间内的最大值。为了确保整个区间排序后,所有值都能到达正确的位置,我们要不断扩展区间直到区间的最大值小于或等于区间的右端 j
  3. 更新代价: 对于找到的区间 [i, j],将区间长度 j - i + 1 加到总代价 ans 上。
  4. 输出结果: 在所有区间都处理完后,输出总代价 ans

代码实现

#include <bits/stdc++.h>
using namespace std;
int a[1000005];
int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        int n, ans = 0;
        cin>>n;
        for (int i = 1; i <= n; i++) 
            cin >> a[i];
        int i = 1;
        while (i <= n)
        {
            if (a[i] == i) 
                i++;
            else 
            {
                int maxv = a[i];
                int j = i + 1;
                maxv = max(maxv, a[j]);
                while (maxv > j)
                {
                    j++;
                    maxv = max(maxv, a[j]);
                }
                ans += j - i + 1;
                i = j + 1;
            }
        }
        cout << ans << endl;
    }
    return 0;
}

【深基13.例1】查找

在这里插入图片描述

算法思路

因为数据量大,首先想到用二分查找,因为他要找到序列中第一次出现该元素的位置,所以与一般二分查找不同的地方在于即使查找到了和目标数相同的数的位置,但仍然无法确定这个位置是否是第一次出现的,所以要将high指针左移继续查找。
可以记住结论:找到目标后往左查找是找第一次出现的位置,往右查找是找最后一次出现的位置

代码实现

n, m = map(int, input().split())
a = list(map(int, input().split()))
nums = list(map(int, input().split()))
for num in nums:
    low, high = 0, len(a) - 1
    result = -1
    while low <= high:
        mid = (low + high) // 2
        if a[mid] < num:
            low = mid + 1
        elif a[mid] > num:
            high = mid - 1
        else:
            result = mid
            high = mid - 1  
    print(result + 1 if result != -1 else -1, end=' ')

烦恼的高考志愿

在这里插入图片描述

算法思路

直接利用pythonbisect模块中的bisect_left()函数来找到最大的且小于这个分数的录取分数,需要注意一下这个函数返回值,做一下特殊判断。由题意可知可以选择分数线比估分高的学校,所以要找到离这个估分最近的一大一小两个分数,取差值最小的

代码实现

import bisect
m,n=map(int, input().split())
sc=list(map(int, input().split()))
st=list(map(int, input().split()))
sc.sort()
ans=0
for gr in st:
    ind=bisect.bisect_left(sc,gr,0,m)
    if ind==0:
        near=abs(sc[0] - gr)
    elif ind==m:
        near=abs(sc[-1] - gr)
    else:
        near=min(abs(sc[ind] - gr), abs(sc[ind-1] - gr))
    ans+=near
print(ans)

Piano

在这里插入图片描述

题目解析

给你一个字符串p=wbwbwwbwbwbwwbw他可以无限往后循环下去,输入WB两个数,请判断能不能在这个无限的字符串中找一个连续子串里面刚好有W'w'B'b'.

算法思路

字符串p的长度为12,因为这个字符串串可以无限循环下去,所以所有子串的起始位置只有12种可能,而我们只需要在这12个起点枚举一下,往后判断之后的W+B个元素中'w''b'的个数是否满足刚好等于WB就行了,同样的,因为字符串p可以无限循环,所以只需要取余就能找到相应位置的字母。

代码实现

W,B=map(int, input().split())
p="wbwbwwbwbwbwwbw"
for i in range(12):
    w=0
    b=0
    for j in range(W + B):
        if p[(i + j) % 12] == 'w':
            w+=1
        else:
            b+=1
    if W==w and B==b:
        print("Yes")
        exit()
print("No")

Rudolf and 121

在这里插入图片描述

算法思路

由于选择的索引范围是[2,n−1],而且如果把每次操作的三个数的下标变成i,i+1,i+2,那么遍历的范围就变成[1,n-2],然后开始遍历并模拟,因为要从左到右把整个序列变成0,每次i+1位置上的操作次数取决于a[i],一旦出现了负数,那么说明是不可能的。如果从左到右操作完成后全变成了0,那么说明是可以的。

代码实现

t = int(input())
for _ in range(t):
    n = int(input())
    a = [0]+list(map(int, input().split()))
    f=1
    for i in range(1,n-1):
        if a[i] < 0:
            f=0
            break
        a[i + 1] -= a[i] * 2
        a[i + 2] -= a[i]
        a[i] -= a[i]
    if a.count(0) ==n+1 and f==1:
        print("YES")
    else:
        print("NO")

Transportation Expenses

在这里插入图片描述

题目解析

找到一个数,与序列中每个数比较后取较小者累加,累加和在不超过M的条件下,这个数最大能是多少

算法思路

显然这题可以用二分答案来做,我这里是把上限设成了序列中最大的数,但是应该还不是最优的。要注意特殊情况的判断,就是如果序列中所有元素加起来都不超过M,那么要找到的这个数可以无限大。

代码实现

def check(mid, a, m):
    total = sum(min(mid,x) for x in a)
    return total <= m
n,m=map(int, input().split())
a=list(map(int, input().split()))
if sum(a) <= m:
    print('infinite')
else:
    l,r = 0,max(a)
    result=0
    while l<=r:
        mid=(l+r)//2
        if check(mid, a, m):
            result = mid 
            l=mid+1  
        else:
            r=mid-1 
    print(result)

AtCoder Janken 3

在这里插入图片描述

题目解析

两个人剪刀石头布,你已知对方将要出的所有手势,你不能连续出两个同样的手势,而且你只能赢或者打平,问你最多可以赢几把。

算法思路

动态规划

代码实现

n = int(input())
s = " " + input()  
dp = [[0, 0, 0] for _ in range(n + 2)]
for i in range(1, len(s)):
    if s[i] == 'R':
        dp[i][0] = max(dp[i - 1][2], dp[i - 1][1])
        dp[i][1] = max(dp[i - 1][0], dp[i - 1][2]) + 1
    elif s[i] == 'P':
        dp[i][1] = max(dp[i - 1][0], dp[i - 1][2])
        dp[i][2] = max(dp[i - 1][0], dp[i - 1][1]) + 1
    else:
        dp[i][2] = max(dp[i - 1][0], dp[i - 1][1])
        dp[i][0] = max(dp[i - 1][1], dp[i - 1][2]) + 1
if s[-1] == 'R':
    print(max(dp[n][0], dp[n][1]))
elif s[-1] == 'P':
    print(max(dp[n][1], dp[n][2]))
else:
    print(max(dp[n][0], dp[n][2]))

学习笔记

贪心算法

定义

(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。 贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素。

总结

主要看生活经验,当发现一个问题的解决只需要考虑最优子结构的问题即可,即每一步最优不需要考虑整体,而这时就可以用我们的贪心算法来解决问题。

双指针

定义

双指针即用两个不同速度或不同方向的指针对数组或对象进行访问,通过两个不同指针的碰撞从而达到特定的目的。

总结

左右指针通常在数组有序的情况下,从最小和最大端同时对数组进行处理,对满足特定条件的数组元素进行成对处理,快慢指针逐渐靠拢直至发生碰撞,则遍历完所有数组。快慢指针中的快慢即两个指针移动的快慢不同,通过两个指针移动速度的不同,判断数组或链表的长度、是否有环、特定位置的数值等。

前缀和

定义

对于一个给定的数列A,他的前缀和数中 S 中 S[ i ] 表示从第一个元素到第 i 个元素的总和。

一维前缀和

前缀和数组的每一项是可以通过原序列以递推的方式推出来的,递推公式就是:

S[ i ] = S[ i - 1 ] + A[ i ]

S[ i - 1 ] 表示前 i - 1 个元素的和,在这基础上加上 A[ i ],就得到了前 i 个元素的和 S [ i ]。

考虑以下问题
给你n个数字,和q次询问,每次询问给你两个数l,r,求区间[l,r]的和。
洛谷P8218
这里当数据量很大的时候暴力解决时间复杂度将会很高,如果利用前缀和对这n个数字预处理
将会大大降低时间复杂度。
代码如下

//给你n个数字,和q次询问,每次询问给你两个数l,r,求区间[l,r]的和。
#include <bits/stdc++.h>
using namespace std;
int a[100000];
int sum[100000];//s[i]前i项和
int main() {
    int n;cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
    }
    int t;cin >> t;
    while (t--) {
        int l, r;cin >> l >> r;
        cout << sum[r] - sum[l - 1] << endl;
    }
}

差分

定义

就是前缀和的逆运算,差分数组的前缀和数组就是原数组,递推公式是

B [ i ] = A[ i ] - A[i - 1]

设 原 数 组 :5 1 3 3 5 5 2
则差分数组:5 -4 2 0 2 0 -3
如果我们想使x-y范围内的值都增加z:在差分数组操作

B[x] + z;
B[y + 1] - z;

然后对差分数组求一次前缀和就能得到改变后的数组。
在这里插入图片描述
对于每一个操作 (x, y, z),表示要将区间 [x, y] 的每个元素增加 z。差分数组 b 中的 b[x] 加上 zb[y + 1] 减去 z。这样做的目的是通过差分数组记录区间增量。

  • 遍历 b 数组,将差分数组的累加和应用到原始数组 a 中,b[i] 累加前一个值 b[i - 1] 以获取真正的区间更新值。
  • 更新 a[i] 的值为 a[i] + b[i]
  • 同时记录更新后的 a[i] 的最小值。
#include <bits/stdc++.h>
using namespace std;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int n, p;
    cin >> n >> p;
    vector<int> a(n + 1, 0); 
    vector<int> b(n + 2, 0); 
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    while (p--)
    {
        int x, y, z;
        cin >> x >> y >> z;
        b[x] += z;
        b[y + 1] -= z; 
    }

    int minn = INT_MAX; 
    for (int i = 1; i <= n; i++)
    {
        b[i] += b[i - 1];
        a[i] += b[i];
        minn = min(minn, a[i]);
    }
    cout << minn << endl;
}
//另一种写法
#include <bits/stdc++.h>
using namespace std;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int n, p;
    cin >> n >> p;
    vector<int> a(n + 1, 0);
    vector<int> b(n + 2, 0);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        b[i] = a[i] - a[i - 1];
    }
    while (p--)
    {
        int x, y, z;
        cin >> x >> y >> z;
        b[x] += z;
        b[y + 1] -= z;
    }

    int minn = INT_MAX;
    for (int i = 1; i <= n; i++)
    {
        a[i] = b[i] + a[i - 1];
        minn = min(minn, a[i]);
    }

    cout << minn << endl;
}

二维前缀和,差分

因为差分可以看成前缀和的逆运算,可以从矩阵前缀和的构造中反推出差分矩阵。

二维前缀和的递推公式为b[i][j] = b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1] + a[i][j]

这里b是前缀和矩阵,a是差分矩阵。

所以可以反推出a[i][j] = b[i][j] - b[i - 1][j] - b[i][j - 1] + b[i - 1][j - 1]

递推公式有了就可以构造差分矩阵,为了方便理解和书写,定义矩阵时要多定义一行和一列(差分数组要多定义两行两列,之后区间加减数时会用到,使之不超出索引范围)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3+100;
int a[maxn][maxn];
int sum[maxn][maxn];
int main() {
    int n;cin >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> a[i][j];
            sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j];
        }
    }
    //通过前缀和数组求区间(x1,y1)到(x2,y2)的和
    int x1,y1,x2,y2;cin >> x1 >> y1 >> x2 >> y2;
    cout << sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1] << endl;
    return 0;
}

例题:求和最大的子矩阵 洛谷P1719
和上一题同理,加一个四重循环枚举子矩阵的左上角和右下角坐标。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 100;
int a[maxn][maxn];
int sum[maxn][maxn];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> a[i][j];
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
        }
    }
    int maxsum = 0;
    // 求最大加权和的矩形
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            for (int k = i; k <= n; k++)
            {
                for (int l = j; l <= n; l++)
                {
                    int t = sum[k][l] - sum[i - 1][l] - sum[k][j - 1] + sum[i - 1][j - 1];
                    if (t > maxsum)
                    {
                        maxsum = t;
                    }
                }
            }
        }
    }
    cout << maxsum << endl;
}

二分查找

二分查找算法是一种高效的搜索方法复杂度是O(logn),适用于在有序数组中查找目标元素。它通过逐步将搜索范围减半,从而迅速缩小搜索范围。二分查找分为整数二分查找和实数二分查找两大板块,各有其适用场景和实现方式。

一、整数二分查找

定义:
整数二分查找是针对整型数据的查找算法。它要求数据集必须是有序的,即数据必须按照某种顺序排列,如升序或降序。
步骤:

  1. 初始化边界: 设定搜索范围的初始边界。对于一个数组 arr,通常设置 low 为数组的起始索引(0),high 为数组的结束索引(n-1)。

  2. 计算中间索引: 计算当前搜索范围的中间位置 mid,通常使用公式 mid = low + (high - low) / 2。这种计算方式避免了可能的整数溢出。

  3. 比较中间元素: 将中间元素 arr[mid] 与目标值 target 比较:

    • 如果 arr[mid] 等于目标值,则搜索成功,返回中间索引 mid
    • 如果 arr[mid] 小于目标值,则目标值在中间元素的右侧,将 low 更新为 mid + 1
    • 如果 arr[mid] 大于目标值,则目标值在中间元素的左侧,将 high 更新为 mid - 1
  4. 重复过程: 继续在更新后的范围内重复上述步骤,直到找到目标值或 low 大于 high 为止。
    时间复杂度: 二分查找的时间复杂度为 O(log n),其中 n 是数组的大小。由于每次搜索都将范围缩小一半,因此其效率非常高。
    示例代码(Python):

def binary_search(arr, target):
    low, high = 0, len(arr) - 1
    while low <= high:
        mid = low + (high - low) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1
    return -1  # 目标值不存在
二、实数二分查找

定义:
实数二分查找用于在有序的实数范围内查找目标值。不同于整数二分查找,实数二分查找主要用于查找某个精度下的近似值。

步骤:

  1. 初始化边界: 确定搜索范围的初始边界 lowhigh。例如,可以设定 low 为搜索范围的下限,high 为搜索范围的上限。

  2. 计算中间值: 计算当前搜索范围的中间值 mid,公式为 mid = (low + high) / 2

  3. 比较中间值: 将中间值与目标值 target 比较:

    • 如果中间值接近目标值(根据设定的精度),则返回中间值或其索引。
    • 如果中间值小于目标值,则目标值在中间值的右侧,将 low 更新为 mid
    • 如果中间值大于目标值,则目标值在中间值的左侧,将 high 更新为 mid
  4. 重复过程: 继续在更新后的范围内重复上述步骤,直到满足精度要求或 lowhigh 的差值小于设定的精度阈值为止。

时间复杂度: 实数二分查找的时间复杂度也是 O(log n),其中 n 表示搜索精度的迭代次数。

示例代码(Python):

def real_number_binary_search(low, high, target, precision=1e-6):
    while (high - low) > precision:
        mid = (low + high) / 2
        if mid == target:
            return mid
        elif mid < target:
            low = mid
        else:
            high = mid
    return (low + high) / 2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值