第三周算法题(前缀和,单调栈,KMP)

第三周(10.6-10.12):

第一题:

题目来源:https://www.acwing.com/problem/content/797/与https://www.acwing.com/problem/content/799/。

题目描述:本题为两道题目组合而成,彼此之间是运算与逆运算的关系。

题目一:

输入一个长度为 n 的整数序列。

接下来再输入 m 个询问,每个询问输入一对 l,r。

对于每个询问,输出原序列中从第 l个数到第 r个数的和。

输入格式

第一行包含两个整数 n 和 m。

第二行包含 n 个整数,表示整数数列。

接下来 m行,每行包含两个整数 l 和 r,表示一个询问的区间范围。

输出格式

共 m 行,每行输出一个询问的结果。

数据范围
1≤l≤r≤n,
1≤n,m≤1000001000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
解题思路:本题主要运用了前缀和的算法,将每次查找的时间复杂度变成o(1),极大节约了时间。
解题代码:
#include <stdio.h> // 引入标准输入输出库
#define N 100010 // 定义常量N为100010
int main(){ // 主函数
  int n, m; // 定义两个整型变量n和m
  scanf("%d %d",&n,&m); // 从标准输入读取两个整数赋值给n和m
  int i,j; // 定义两个整型变量i和j
  int arr[N]; // 定义一个长度为N的整型数组arr
  for(i=0;i<n;i++){ // 对于i从0到n-1
  	scanf("%d",&arr[i]); // 从标准输入读取一个整数赋值给arr[i]
  } 
  int s[N]={0}; // 定义一个长度为N的整型数组s,并初始化所有元素为0
  for(i=0;i<n;i++){ // 对于i从0到n-1
  	s[i+1]=s[i]+arr[i]; // 计算前i+1个数的和存储在s[i+1]中
  }
  while(m--){ // 循环m次
   	int l,r; // 定义两个整型变量l和r
   	scanf("%d %d",&l,&r); // 从标准输入读取两个整数赋值给l和r
   	printf("%d\n",s[r]-s[l-1]); // 输出s[r]-s[l-1],即arr数组中从第l个数到第r个数的和
   }    
return 0; // 程序正常结束,返回0
}

题目二:

题目描述:

输入一个长度为 n 的整数序列。

接下来输入 m个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。

请你输出进行完所有操作后的序列。

输入格式

第一行包含两个整数 n 和 m。

第二行包含 n个整数,表示整数序列。

接下来 m行,每行包含三个整数 l,r,c,表示一个操作。

输出格式

共一行,包含 n个整数,表示最终序列。

数据范围
1≤n,m≤100000
1≤l≤r≤n
−1000≤c≤10001000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
解题思路:本题运用到了差分的算法,每次查找的时间复杂度变成o(1),差分是前缀和的逆运算,要人为的构造一个数组,使已有的数组为它的前缀和,对构造数组某一项加减,等同于将原数组该项后所有项加减。
解题代码:
#include <stdio.h> // 引入标准输入输出库
int main(){ // 主函数
    int n,m,i; // 定义三个整型变量n,m和i
    scanf("%d %d",&n,&m); // 从标准输入读取两个整数赋值给n和m
    int arr[100005]={0}; // 定义一个长度为100005的整型数组arr,并初始化所有元素为0
    for(int i=0;i<n;i++){ // 对于i从0到n-1
        scanf("%d",&arr[i]); // 从标准输入读取一个整数赋值给arr[i]
    }
    int cf[10005]={0}; // 定义一个长度为10005的整型数组cf,并初始化所有元素为0
    for(i=0;i<n;i++){ // 对于i从0到n-1
        if(i==0){ // 如果i为0
            cf[0]=arr[0]; // 将arr[0]赋值给cf[0]
        }
        else{ // 否则
            cf[i]=arr[i]-arr[i-1]; // 计算arr[i]和arr[i-1]的差值赋值给cf[i]
        }
    }
    while(m--){ // 循环m次
        int a,b,c; // 定义三个整型变量a,b和c
        scanf("%d %d %d",&a,&b,&c); // 从标准输入读取三个整数赋值给a,b和c
        a--; // a减1
        b--; // b减1
        cf[a]+=c; // 将c加到cf[a]上
        cf[b+1]-=c; // 从cf[b+1]减去c
    }
    int sum=0; // 定义一个整型变量sum并初始化为0
    for(i=0;i<n;i++){ // 对于i从0到n-1
        sum+=cf[i]; // 将cf[i]加到sum上
        printf("%d ",sum); // 输出sum和一个空格
    }
return 0; // 程序正常结束,返回0
}

第二题:

题目来源:https://www.acwing.com/problem/content/832/

题目描述

给定一个长度为 N的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。

输入格式

第一行包含整数 N,表示数列长度。

第二行包含 N个整数,表示整数数列。

输出格式

共一行,包含 N个整数,其中第 i个数表示第 i个数的左边第一个比它小的数,如果不存在则输出 −1。

数据范围

1≤N≤100000

1≤数列中元素≤10e9

输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2
解题思路:
本题运用了模拟栈和单调递增栈两种思想,我们可以发现,如果在新元素左侧存在递减的数列,那么那个大数永远也不会输出。
20201211221031165.gif
解题代码:
#include <stdio.h>
int main(){
    int n;
    scanf("%d",&n);
    int arr[n];
    int i=0;
    for(i=0;i<n;i++){
        scanf("%d",&arr[i]);
    }
    int stk[10005]={-1};
    int idx=0;
    for(i=0;i<n;i++){
        if(arr[i]>stk[idx]){
            printf("%d ",stk[idx]);
            stk[++idx]=arr[i];
            continue;
        }
        else{
            for(idx;idx>=0;idx--){
                if(stk[idx]>=arr[i]){
                    stk[idx]=0;
                }
                else{
                printf("%d ",stk[idx]);
                stk[++idx]=arr[i];
                break;
                }
            }
        }
    }  

return 0;

}
  1. 首先,程序从标准输入读取一个整数n,然后创建一个长度为n的数组arr
  2. 然后,程序从标准输入读取n个整数,并将它们存储在数组arr中。
  3. 接着,程序创建一个栈stk,并初始化栈顶索引idx为0。
  4. 程序遍历数组arr。对于每个元素arr[i],如果它大于栈顶元素stk[idx],则程序输出栈顶元素,并将arr[i]压入栈中。否则,程序从栈顶开始向下遍历栈,直到找到一个元素小于arr[i],然后输出该元素,并将arr[i]压入栈中。

第三题:

题目来源:https://www.acwing.com/problem/content/833/
题目描述:

给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模式串 P在字符串 S中多次作为子串出现。

求出模式串 P在字符串 S中所有出现的位置的起始下标。

输入格式

第一行输入整数 N,表示字符串 P 的长度。

第二行输入字符串 P。

第三行输入整数 M,表示字符串 S 的长度。

第四行输入字符串 S。

输出格式

共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。

数据范围

1≤N≤10^5
1≤M≤10^6

输入样例:
3
aba
5
ababa
输出样例:
0 2
解题思路:本题将用到KMP算法,KMP算法,全称Knuth-Morris-Pratt算法,是一种著名的字符串匹配算法,它的核心思想是利用已经部分匹配的有效信息,使得后续的匹配尝试能够跳过那些肯定不会匹配成功的位置,从而提高匹配效率。KMP算法的主要组成部分是一个被称为next数组的辅助数据结构],next数组的定义是:对于模式串P,next[i]表示P[0] ~ P[i]这一个子串中,使得前k个字符恰等于后k个字符的最大的k,KMP算法的时间复杂度是O(n+m),其中n和m分别是主串和模式串的长度,使得KMP算法在处理字符串匹配问题时,相比暴力匹配算法具有更高的效率,在每次失配时,不是把匹配串往后移一位,而是把它往后移动至下一次可以和前面部分匹配的位置,这样就可以跳过大多数的失配步骤。而每次移动的步数就是通过查找next[ ]数组确定的。
解题代码:
先上一个较为暴力的版本(其实已经优化的很快了):
#include <stdio.h>
int main(){
   int n;
   scanf("%d\n",&n);
   char str1[100005];
   scanf("%s",str1);
   getchar();
   int m;
   scanf("%d\n",&m);
   char str2[1000005];
   scanf("%s",str2);
   char*p=str2;
   char*place;
   while((place=strstr(str2,str1))!=NULL){
       printf("%d ",place-p);
       int k=1;
               while(k--){
            *place='.';
            place++;
        }
   }
    return 0;
}
再来看看kmp:
#include <stdio.h>
int main() {
	int n;
	scanf("%d\n", &n);
	char str1[10008];
	scanf("%s", str1);
	getchar();
	int m;
	scanf("%d\n", &m);
	char str2[1000900];
	scanf("%s",str2);
	int next[100090];
	int i = 1, j = 0;
	next[0] = 0;
	for (i = 1; i < n; i++) {
		while (j > 0 && str1[i] != str1[j]) {
			j = next[j - 1];
		}
		if (str1[i] == str1[j]) {
			j++;
		}
		next[i] = j;
	}
	j = 0;
	for (i = 0; i < m;) {
		if (str2[i] == str1[j]) {
			i++;
			j++;
			if (j == n) {
				printf("%d ", i - n );
				if (j > 0) {
					j = next[j - 1];
				}
			}
		}
		else if (j > 0) {
			j = next[j - 1];
		}
		else {
			i++;
		}
	}
	return 0;
}
这段代码是KMP算法的C语言实现。下面是对这段代码的详细解释:
  1. 首先,程序从标准输入读取两个字符串:str1str2,以及它们的长度nm
  2. 然后,程序创建一个名为next的数组,用于存储模式串str1的部分匹配表。
  3. 在计算next数组的过程中,程序遍历模式串str1,并使用两个指针ij。其中i是主指针,j是子指针。如果str1[i]等于str1[j],则j增加1,并将j的值赋给next[i]。如果str1[i]不等于str1[j],则j回退到next[j-1],直到找到一个位置使得str1[i]等于str1[j]或者j等于0。
  4. 在匹配阶段,程序使用两个指针ij分别遍历目标串str2和模式串str1。如果str2[i]等于str1[j],则两个指针都向前移动一位。如果str2[i]不等于str1[j],则j回退到next[j-1],直到找到一个位置使得str2[i]等于str1[j]或者j等于0。如果j等于n,说明找到了一个匹配,然后输出匹配的位置,并将j回退到next[j-1]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xiao Ling.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值