1.台阶问题:
这道题目一看其实很容易想到可以用dp的板子去做,并且只需要用一维dp即可,其中dp的下标表示到达当前阶梯总共有多少种方法,由于结果有可能会很大所以一定要记得边记录边模,代码实现如下:
#include<bits/stdc++.h>
using namespace std;
const int mod = 100003;
int n,k;
int dp[100010];
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin>>n>>k;
dp[0]=dp[1]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<=k;j++){
if(i>=j){
dp[i]=(dp[i]+dp[i-j]) % mod;
}
}
}
cout<<dp[n]%mod;
return 0;
}
2.递增三元组:
这一道题目的意思是比较好理解的,我们需要考虑的是,由于给的N的极限值是1e5,所以暴力的方式时间复杂度n的三方肯定会爆,于是我们就要考虑如何对时间复杂度进行优化。
我们可以这样进行考虑,由于数组b是中间数组,我们不妨先对三个数组进行排序,然后我们对b进行枚举,对于每一次枚举,我们在a数组中找到所有小于当前枚举对应的b数组下标元素的个数,然后再找到c数组中所有大于当前b数组下标所对应的元素个数,再把当前符合条件的总个数加到ans上面即可。
再继续思考我们对于每次枚举,然后在a,c数组中查找符合要求的,都可以通过二分进行查找从而降低时间复杂度。所以代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int num[3][N];
int main() {
int n;
scanf("%d", &n);
for(int i = 0; i < 3; ++i)
for(int j = 1; j <= n; ++j)
scanf("%d", &num[i][j]);
for(int i = 0; i < 3; ++i)
sort(num[i]+1, num[i]+n+1);
LL ans = 0;
//枚举B,寻找A满足的个数以及C满足的个数相乘
for(int i = 1; i <= n; ++i) {
int key = num[1][i];
//A中二分查找第一个不小于key的数的下标
int pos1 = lower_bound(num[0]+1, num[0]+n+1, key)-num[0]-1;
//C中二分查找第一个大于key的数的下标
int pos2 = upper_bound(num[2]+1, num[2]+n+1, key)-num[2];
if(pos1 >= 1 && pos2 <= n) {
ans += (LL)pos1*(n-pos2+1);
}
}
cout<<ans<<endl;
return 0;
}
这段代码有需要解释的地方:
num[0]+1
:这是指向num
数组的第一个元素的下一个位置的指针。换句话说,它指向num
数组中的第二个元素。num[0]+n+1
:这是指向num
数组的最后一个元素的下一个位置的指针。换句话说,它指向num
数组之后的第一个位置。lower_bound(num[0]+1, num[0]+n+1, key)
:这是在num
数组的第二个元素和最后一个元素之间(不包括最后一个元素)查找第一个不小于key
的元素的迭代器。lower_bound(...)-num[0]-1
:这将返回的迭代器与num
数组的第一个元素的指针相减,然后减去1。这样,我们得到的是找到的元素在num
数组中的索引。
所以,pos1
存储的是key
在num
数组中的位置,如果key
不在num
数组中,则pos1
将是key
应该插入的位置,以保持num
数组的有序性。
简而言之,这段代码在num
数组中查找key
的位置,并返回其索引。如果key
不在数组中,则返回应该插入key
的位置。
在C++中,指针的算术运算实际上是对指针所指向的内存地址进行算术运算。当两个指针指向同一个数组或同一个对象的成员时,它们之间的差值表示两个元素之间的偏移量,这个偏移量是以元素为单位,而不是以字节为单位。
对于数组num
,num[0]
是一个指向数组第一个元素的指针。当我们执行lower_bound
函数时,得到的是一个迭代器,它指向数组中第一个不小于key
的元素。这个迭代器也是一个指针,指向数组中的某个元素。
当从lower_bound
返回的迭代器中减去num[0]
时,得到的是两个指针之间的偏移量,这个偏移量表示从数组的第一个元素到找到的元素之间有多少个元素。因为lower_bound
返回的迭代器指向的是数组中的元素,而不是元素之间的空隙,所以这个偏移量实际上就是找到的元素在数组中的下标(从0开始计数)。
最后,减去1,是因为我们希望得到的是下标而不是偏移量。在C++中,数组的下标是从0开始的,而指针的偏移量是从1开始的(即,指向第一个元素的指针偏移量为0,指向第二个元素的指针偏移量为1,依此类推)。因此,减去1将偏移量转换为正确的下标。
所以,lower_bound(...)-num[0]-1
的结果就是找到的元素在数组中的下标。
第二种方法:双指针优化:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int num[3][N];
int main() {
int n;
scanf("%d", &n);
for(int i = 0; i < 3; ++i)
for(int j = 1; j <= n; ++j)
scanf("%d", &num[i][j]);
for(int i = 0; i < 3; ++i)
sort(num[i]+1, num[i]+n+1);
LL ans = 0;
//枚举B,寻找A满足的个数以及C满足的个数相乘
int a = 1, c = 1;
for(int i = 1; i <= n; ++i) {
int key = num[1][i];
while(a<=n && num[0][a] < key) a++;
while(c<=n && num[2][c] <= key) c++;
ans += (LL)(a-1)*(n-c+1);
}
cout<<ans<<endl;
return 0;
}
其实也就是将二分中的部分更改了一下。
之前的双指针算法时间复杂度的瓶颈为:排序O(nlog2n)
考虑是否可以不排序在O(n)的时间内解决此问题呢?
既然要排序实现快速的查找A中小于B[i]的数的个数,可以将数组A中所有元素出现的次数存入一个哈希表中,因为数组中元素的范围只有n5, 可以开一个大的数组cnta 作为哈希表。
在枚举B中元素时,我们需要快速查找找小于B[i]的所有元素的总数,只需要在枚举之前先将求出表中各数的前缀和即可。
查找C与查找A同理可得。
代码如下:
//前缀和
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int A[N], B[N], C[N];
int cnta[N], cntc[N], sa[N], sc[N];
int main() {
int n;
scanf("%d", &n);
//获取数i在A中有cntc[i]个,并对cnt求前缀和sa
for(int i = 1; i <= n; ++i) {
scanf("%d", &A[i]);
cnta[++A[i]]++;
}
sa[0] = cnta[0];
for(int i = 1; i < N; ++i) sa[i] = sa[i-1]+cnta[i];
//B只读取即可
for(int i = 1; i <= n; ++i) scanf("%d", &B[i]), B[i]++;
//获取数i在C中有cntc[i]个,并对cnt求前缀和sc
for(int i = 1; i <= n; ++i) {
scanf("%d", &C[i]);
cntc[++C[i]]++;
}
sc[0] = cntc[0];
for(int i = 1; i < N; ++i) sc[i] = sc[i-1]+cntc[i];
//遍历B求解
LL ans = 0;
for(int i = 1; i <= n; ++i) {
int b = B[i];
ans += (LL)sa[b-1] * (sc[N-1] - sc[b]);
}
cout<<ans<<endl;
return 0;
}
感谢您的观看。