一、学习到的知识点
1、贪心算法
本质:
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。
优点:
贪心算法可以快速找到问题的可行解,因此对于要求不是很严格的问题,贪心算法可以作为一种简单而有效的解决方案。
缺点:
贪心算法不保证会得到最优解。
2、双指针算法
本质:
严格的来说,双指针只能说是是算法中的一种技巧。
双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。最常见的双指针算法有两种:一种是,在一个序列里边,用两个指针维护一段区间;另一种是,在两个序列里边,一个指针指向其中一个序列,另外一个指针指向另外一个序列,来维护某种次序。
何时使用:
在利用双指针算法解题时,考虑原问题如何用暴力算法解出,观察是否可构成单调性,若可以,就可采用双指针算法对暴力算法进行优化.
3、前缀和数组 和 差分数组
前缀和数组:
这个概念很好理解,直接上代码:
const int N = 1e5 + 10;
int sum[N], a[N]; //sum[i]=a[1]+a[2]+a[3].....a[i];
for(int i = 1;i <= n; i++)
{
sum[i] = sum[i - 1] + a[i];
}
二维前缀和数组:
看图理解巨简单~~~
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int s[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &s[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
while (q -- )
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
}
return 0;
}
差分数组:
类似于数学中的求导和积分,差分可以看成前缀和的逆运算。
怎么说呢,一般在做题过程中我们可能用不到图中这些东西,因为我们有更简便的方法。比如说一般会用在——使数组{1,2,3,4,5,6}中,让第二个数到第四个数全部加一,我们可以:
const int N = 1e7 + 10;
int a[N],b[N];
void run(){
int n,q;
cin >> n >> q;
for(int i = 1;i <= n;i++){
cin >> a[i];
}
while(q--){
int x,y;
cin >> x >> y;
b[x] += 1;
b[y + 1] -= 1;
}
for(int i = 1;i <= n;i++){
b[i] = b[i - 1] + b[i];
a[i] = a[i] + b[i];
}
}
4、map和pair
PS:该知识点是做到T1的时候用到的
#include <map>
using namespace std;
const int N = 1e5 + 10;
map<pair<int,int>,bool> mp;
int A,B;
for(int i = 1;i <= r;i++){
cin >> A >> B;
if(mp[make_pair(A,B)]){
continue;
}
mp[make_pair(A,B)] = true;
mp[make_pair(B,A)] = true;
}
二、题解
t1、前缀和与差分练习 G - Tallest Cow
题解:
先把所有牛的高度设为最高值,如果某两头牛之间能互相看到的话,那直接把中间所有牛都砍一刀(中间这些牛的高度都减一)然后最后输出就可以了。
但是!!!!!A与B存在重复的可能,所以一定一定一定要查查!!!(是谁aw了5次还没找出问题QAQ)。但是用二维数组去存储A和B显然会超限,我们可以有map与pair来查重。
代码:
#include <iostream>
#include <map>
#include<utility>
using namespace std;
const int N = 1e5 + 10;
int a[N],b[N];
map<pair<int,int>,bool> mp;
int A,B;
void run(){
int n,I,h,r;
cin >> n >> I >> h >> r;
for(int i = 1;i <= n;i++){
a[i] = h;
}
for(int i = 1;i <= r;i++){
cin >> A >> B;
if(mp[make_pair(A,B)]){
continue;
}
mp[make_pair(A,B)] = true;
mp[make_pair(B,A)] = true;
if(A < B){
b[A + 1] -= 1;
b[B] += 1;
}
else if(A > B){
b[B + 1] -= 1;
b[A] += 1;
}
}
for(int i = 1;i <= n;i++){
b[i] = b[i - 1] + b[i];
a[i] = a[i] + b[i];
cout << a[i] << "\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T = 1;
//cin >> T;
while(T--){
run();
}
return 0;
}
T2、贪心,前缀和,双指针 E-学生分组
题解:
这题第一次做的时候尝试过暴力,但是aw了……
于是我突然想到这毕竟不需要考虑每一个人
所以我们可以先判断不合法的情况:
当总和大于合法总和时,就是不合法的.
当总和小于合法总和时,就是不合法的
那么:我们可以对Ai 进行分析:
如果Ai 大于等于L且小于等于R, 不需要处理.
如果Ai大于R,那么因为总和S在合法总和区间内,所以总可以找到一种方案,使原本在[L,R]之间的Ai保持在[L,R]内,并且填补那些小于L的Ai.(比如直接填补)
如果Ai小于L,逻辑同2.
所以,我们只需判断有多少个Ai>R,多少个Ai<L,然后将这些组数的总和比较,输出较大值即可
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int a[N];
void run(){
int n,ans;
cin >> n;
int sum = 0;
for(int i = 1;i <= n;i++){
cin >> a[i];
sum += a[i];
}
sort(a + 1,a + 1 + n);
int L,R;
cin >> L >> R;
if(sum > n * R || sum < n * L){
cout << -1;
return;
}
if(a[1] >= L && a[n] <= R){
cout << 0;
return;
}
int ans1 = 0;
for(int i = 1;i <= n;i++){
if(a[i] < L){
ans1 += (L - a[i]);
}
else{
break;
}
}
int ans2 = 0;
for(int i = n;i >= 1;i--){
if(a[i] > R){
ans2 += (a[i] - R);
}
else{
break;
}
}
if(ans1 >= ans2){
ans = ans1;
}
else{
ans = ans2;
}
cout << ans;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T = 1;
//cin >> T;
while(T--){
run();
}
return 0;
}
T3、双指针练习 C-子序列
题解:
要得出最短的连续数字和大于等于S,可以先找出满足此条件的连续段,再从中找出最短的。
保存起点终点两个数组下标,先不断挪动终点下标,求和,直到满足条件停止,存下此时连续元素的长度,再移动起点下标,减去前面的数字,更新最短长度,不满足则继续移动终点下标,循环直到数组结束,输出最小长度。这就是双指针。
代码:
#include <iostream>
#include <vector>
#define int long long
using namespace std;
const int N = 1e5 + 10;
vector<int> a;
void run(){
int n,k;
cin >> n >> k;
a.clear();
for(int i = 1;i <= n;i++){
int x;cin >> x;
a.push_back(x);
}
int i = 0;
int j = i;
int s = a[i];
int ans = n;
while(j < n){
while(s < k && j < n - 1){
j++;
s += a[j];
}
if(s < k){
break;
}
while(s >= k){
if(ans > j - i + 1){
ans = j - i + 1;
//cout << i << " " << j << "\n";
}
s -= a[i];
//cout << s << "\n";
i++;
//cout << i << " ";
}
}
if(ans == n){
cout << 0 << "\n";
}
else{
cout << ans << "\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while(T--){
run();
}
return 0;
}