第三周(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≤100000
−1000≤数列中元素的值≤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≤1000
−1000≤整数序列中元素的值≤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
解题思路:
本题运用了模拟栈和单调递增栈两种思想,我们可以发现,如果在新元素左侧存在递减的数列,那么那个大数永远也不会输出。
解题代码:
#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;
}
- 首先,程序从标准输入读取一个整数
n
,然后创建一个长度为n
的数组arr
。 - 然后,程序从标准输入读取
n
个整数,并将它们存储在数组arr
中。 - 接着,程序创建一个栈
stk
,并初始化栈顶索引idx
为0。 - 程序遍历数组
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语言实现。下面是对这段代码的详细解释:
- 首先,程序从标准输入读取两个字符串:
str1
和str2
,以及它们的长度n
和m
。 - 然后,程序创建一个名为
next
的数组,用于存储模式串str1
的部分匹配表。 - 在计算
next
数组的过程中,程序遍历模式串str1
,并使用两个指针i
和j
。其中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。 - 在匹配阶段,程序使用两个指针
i
和j
分别遍历目标串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]
。