2024暑期集训第3周——

一、学习到的知识点

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;
}

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值