前言:如果解决下面这几道题有些问题,或者即使看了我画的过程图也不理解的可以去看看我的上一篇文章,有可能会对你有帮助。
一、《数值元素的目标和》---来自AcWing
数组元素的目标和
给定两个升序排序的有序数组 A和 B,以及一个目标值 。
数组下标从 0 开始。
请你求出满足 A[i]+ B[j]=x 的数对(i,j)。
数据保证有唯一解。
输入格式:
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值。
第二行包含 n 个整数,表示数组 A。
第三行包含 m 个整数,表示数组 B.
输出格式:
共一行,包含两个整数i和 j.
数据范围:
数组长度不超过 1e5
同一数组内元素各不相同。
1 ≤ 数组元素 < 1e9输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
1.暴力解法
这道题暴力解法思路很简单,在这不过多赘述,直接上代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int arr[N], brr[N];
int n, m, x;
int main(void)
{
scanf_s("%d%d%d", &n, &m, &x);
for (int i = 0; i < n; i++)scanf_s("%d", &arr[i]);
for (int i = 0; i < m; i++)scanf_s("%d", &brr[i]);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (arr[i] + brr[j] == x)
{
cout << i << " " << j << endl;
exit(0);
}
}
}
return 0;
}
分析暴力算法的时间复杂度:很明显O(n的平方),数组长度最大是1e5,所以时间复杂度准确为O(1e10),大于1e9,所以要开始想办法优化代码。
2.优化代码
1.画图分析
(看过我前面的文章的都知道我习惯用画图来疏通自己的思维逻辑)
双指针:上面是两种思路进行分析,选取最方便最可行的方式,也就是第二种
2.打代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int arr[N], brr[N];
int n, m, x;
int main(void)
{
scanf_s("%d%d%d", &n, &m, &x);
for (int i = 0; i < n; i++)scanf_s("%d", &arr[i]);
for (int i = 0; i < m; i++)scanf_s("%d", &brr[i]);
for (int i = 0 , j = m-1; i < n ; i++)
{
while (j >= 0 && arr[i] + brr[j] > x)
{
j--;
}
if (arr[i] + brr[j] == x)
{
cout << i << " " << j << endl;
return 0;
}
}
return 0;
}
仔细琢磨while循环所表达的意思,对照我所画的图进行分析,相信理解掌握这道题并不难
二、A-B数对---来自洛谷
给出一串正整数数列以及一个正整数 C,要求计算出所有满足 A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式:
输入共两行。
第一行,两个正整数 N,C。
第二行,N 个正整数,作为要求处理的那串数
输出格式:
一行,表示该串正整数中包含的满足 A−B=C 的数对的个数。
输入样例:
4 1
1 1 2 3
输出样例:
3
1.暴力解法
2.优化代码
1.画图分析:
跟上个题一样,双指针的两种方式,从两个角度进行分析。
2.打代码
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
using ll = long long;
const int N = 2e5 + 10;
int arr[N], cnt[N];
ll n, c;
int main(void)
{
scanf_s("%lld%lld", &n, &c);
ll res = 0;
for (int i = 0; i < n; i++)scanf_s("%d", &arr[i]);
for (int i = 0, j = 0; i < n; i++)
{
while (j < n && arr[i] - arr[j] >= c)
{
if (arr[i] - arr[j] == c)
{
res++;
}
j++;
}
}
cout << res << endl;
return 0;
}
但是这样做是错误的。我们继续进行分析
3.进一步优化
重新找测试用例:
如果我画的图看不懂,可以自己尝试画一画,真的可以使自己的思路变得清晰明了
千万不要忘记排序!!!
4.重新打代码
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
using ll = long long;
const int N = 2e5 + 10;
int arr[N], cnt[N];
ll n, c;
int main(void)
{
scanf_s("%lld%lld", &n, &c);
ll res = 0;
for (int i = 0; i < n; i++)scanf_s("%d", &arr[i]);
sort(arr,arr+n);
for (int i = 0, j1 = 0,j2 = 0; i < n; i++)
{
while (j1 < n && arr[i] - arr[j1] >= c)
{
j1++;
}
while (j2 < j1 && arr[i] - arr[j2] > c)
{
j2++;
}
res += (j1 - j2);
}
cout << res << endl;
return 0;
}
三、递增三元组---来自洛谷
给定三个整数数组 A=[A1,A2,⋯ ,AN],B=[B1,B2,⋯ ,BN],C=[C1,C2,⋯ ,CN]
请你统计有多少个三元组 (i,j,k)(i,j,k) 满足:
- 1≤i,j,k≤N
- Ai<Bj<Ck
输入格式:
第一行包含一个整数 N。
第二行包含 N 个整数 A1,A2,⋯ ,AN
第三行包含 N 个整数 B1,B2,⋯ ,BN
第四行包含 N 个整数 C1,C2,⋯ ,CN
输出格式:
一个整数表示答案
输入样例:
3 1 1 1 2 2 2 3 3 3输出样例:
27
1.暴力解法
思路也是很简单,三层循环枚举就可以
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int arr[N], brr[N], crr[N];
int main(void)
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)cin >> arr[i];
for (int i = 1; i <= n; i++)cin >> brr[i];
for (int i = 1; i <= n; i++)cin >> crr[i];
//sort(arr + 1, arr + n + 1);
//sort (brr + 1, brr + 1 + n);
//sort(crr + 1, crr + 1 + n);
long long res = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
for (int k = 1; k <= n; k++)
{
if (arr[i] < brr[j]&&brr[j] < crr[k])res++;
}
}
}
cout << res << endl;
return 0;
}
2.优化代码
1.可以用二分查找来解决
这里我不多说关于二分的解题过程,还是将过程图画出来,大家感兴趣的可以看看,代码我也会给大家
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n , a[N] , b[N] , c[N] , ans;
signed main(){
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) cin >> b[i];
for(int i = 1; i <= n; i++) cin >> c[i];
sort(a + 1 , a + 1 + n);
sort(c + 1 , c + 1 + n);
//排序,好进行二分
for(int j = 1; j <= n; j++){
int cnta = lower_bound(a + 1 , a + 1 + n , b[j]) - a - 1;
int cntc = upper_bound(c + 1 , c + 1 + n , b[j]) - c;
cntc = n - cntc + 1;
//二分找出i的种类数和j的种类数
ans += cnta * cntc;//乘法原理累计答案
}
cout << ans;
return 0;
}
自定义如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int arr[N], brr[N], crr[N];
int n;
int binary1(int x)
{
int l = 0, r = n + 1;
while (l + 1 != r)
{
int mid = (l + r) / 2;
if (arr[mid] < brr[x])l = mid;
else r = mid;
}
if (arr[l] < brr[x])return l;
else return -1;
}
int binary2(int x)
{
int l = 0, r = n + 1;
while (l + 1 != r)
{
int mid = (l + r) / 2;
if (crr[mid] <= brr[x])l = mid;
else r = mid;
}
if (crr[r] > brr[x])return r;
else return -1;
}
int main(void)
{
cin >> n;
for (int i = 1; i <= n; i++)cin >> arr[i];
for (int i = 1; i <= n; i++)cin >> brr[i];
for (int i = 1; i <= n; i++)cin >> crr[i];
sort(arr + 1, arr + n + 1);
sort(brr + 1, brr + 1 + n);
sort(crr + 1, crr + 1 + n);
long long res = 0, tmp = 0;
for (int i = 1; i <= n; i++)
{
int x = binary1(i);
int y = binary2(i);
if (x == -1 || y == -1)continue;
else
{
tmp = (long long)((x) * (n - y + 1));
res += tmp;
}
}
cout << res << endl;
return 0;
}
2.用双指针来解决
想必认真做了前面两道题的同学,这道题画图之后也会有些思路,看一看自己写的和我写的有什么差别,及时订正!
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
typedef long long LL;
int a[N], b[N], c[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]);
for (int i = 1; i <= n; i ++ ) scanf("%d", &c[i]);
sort(a + 1, a + n + 1);
sort(b + 1, b + n + 1);
sort(c + 1, c + n + 1);//先把数组排序,否则无法双指针
LL ans = 0;//不开long long见祖宗
int cnt = 1, cnt_ = 1;
//双指针,记录a中小于b[i]的个数和c中不大于b[i]的个数
for (int i = 1; i <= n; i ++ ){
while (cnt <= n && a[cnt] < b[i]) cnt ++;//查找a中小于b[i]的个数
while (cnt_ <= n && c[cnt_] <= b[i]) cnt_ ++;//为了方便。实际是在查找c中大于b[i]的个数
ans += (LL) (cnt - 1) * (n - cnt_ + 1);
}
cout << ans;
return 0;
}
四、求和----来自洛谷
题目描述:
给定 n 个整数 a1,a2,⋯ ,an, 求它们两两相乘再相加的和,即
S=a1⋅a2+a1⋅a3+⋯+a1⋅an+a2⋅a3+⋯+an−2⋅an−1+an−2⋅an+an−1⋅an
输入格式:
输入的第一行包含一个整数 nn 。
第二行包含 n 个整数 a1,a2,⋯an
输出格式:
输出一个整数 S,表示所求的和。请使用合适的数据类型进行运算。
输入输出样例
输入:
4 1 3 6 9输出 :
117对于 30% 的数据, 1≤n≤1000,1≤ai≤100 。
对于所有评测用例, 1≤n≤2×1e5,1≤ai≤1000 。
1.暴力解法
这道题的暴力解法就是两层循环,还是不过多赘述:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
int arr[N];
int n;
int main(void)
{
cin >> n;
for (int i = 1; i <= n; i++)cin >> arr[i];
long long res = 0, tmp = 0;
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
tmp += (arr[i] * arr[j]);
res += tmp;
}
}
cout << tmp << endl;
return 0;
}
不过对于这道题来说,暴力解题只能得30分
2.前缀和解法
很明显:我们只要知道怎样求S[n]就能很完美的做出这道题 S[n]的求解就用到了前缀和算法思想
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
int arr[N], s[N];
int n;
int main(void)
{
cin >> n;
long long res = 0;
for (int i = 1; i <= n; i++)
{
cin >> arr[i];
s[i] = s[i-1] + arr[i];
}
for (int i = 1; i <= n; i++)
{
res += (arr[i]*(long long)(s[n]-s[i]));//千万千万别忘记开long long
}
cout << res << endl;
return 0;
}
最后:真心希望这篇文章对你有所帮助!