算法刷题day40

引言

今天复习的是贡献法和模拟/枚举章节,很多题写了,但是我就不一一展示了,因为确实感觉太麻烦了,我只把部分题展示出来。关于这个贡献法,我觉得题型比较单一,就是一看我就大概能知道用这种方法做,或许也是没见过太多题吧。然后就是枚举了,这几年的蓝桥杯主要就是在订单方面考察,不知道今年还出不出了,反正我的这种做法都能做出来,那就继续加油吧!


一、整数删除

标签:模拟、最小堆

思路:用对来存储值及其下标,用 p a i r pair pair 来存刚好,顶点是满足值最小且下标靠前的。然后用 双链表 进行模拟,用小根堆来查找要进行操作的元素,然后按照要求进行操作即可,因为堆中的元素可能会变得跟实际的不一致,所以如果当前元素改变了,那么不操作,并将其真实值放入堆中,进行对应的操作即可。值得注意的点,每个数的范围是在 [ 0 , 1 0 8 ] [0,10^8] [0,108] 之间,并且 1 ≤ K ≤ 5 e 5 1 \leq K \leq 5e5 1K5e5 ,会爆 i n t int int ,所以得用 l o n g   l o n g long\ long long long来存。

题目描述:

给定一个长度为 N 的整数数列:A1,A2,...,AN。

你要重复以下操作 K 次:

每次选择数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除,并把与它相邻的整数加上被删除的数值。

输出 K 次操作后的序列。

输入格式
第一行包含两个整数 N 和 K。

第二行包含 N 个整数,A1,A2,A3,...,AN。

输出格式
输出 N−K 个整数,中间用一个空格隔开,代表 K 次操作后的序列。

数据范围
对于 20% 的数据,1≤K<N≤10000。
对于 100% 的数据,1≤K<N≤5×105,0≤Ai≤108。

输入样例:
5 3
1 4 2 8 7
输出样例:
17 7
样例解释
数列变化如下,中括号里的数是当次操作中被选择的数:

[1] 4 2 8 7
5 [2] 8 7
[7] 10 7
17 7

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<LL,int> PII;
#define x first
#define y second

const int N = 5e5+10;

int n, m;
int l[N], r[N];
LL e[N];

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n >> m;
	r[0] = 1, l[n+1] = n;
	
	priority_queue<PII, vector<PII>, greater<PII>> heap;
	for(int i = 1; i <= n; ++i)
	{
		LL t; cin >> t;
		l[i] = i - 1, r[i] = i + 1, e[i] = t;
		heap.push({t,i});
	}
	
	while(m--)
	{
		auto t = heap.top(); heap.pop();
		LL v = t.x;
		int p = t.y;
		
		if(v != e[p])
		{
			m++;
			heap.push({e[p],p});
		}
		else
		{
		    r[l[p]] = r[p], l[r[p]] = l[p];
            e[l[p]] += e[p], e[r[p]] += e[p];
		}
	}
	
	for(int i = r[0]; i != n + 1; i = r[i]) cout << e[i] << " ";
	
	return 0;
}

二、孤独的照片

标签:贡献法

思路:这道题可以用贡献法,为什么能用呢,因为之前做过。我们可以遍历一边,找出每个奶牛左边和右边第一个和它是同一个奶牛的下标,然后再依次遍历每一个奶牛,我们可以根绝下标,算出当前奶牛,左边和右边有多少个连续不同奶牛的数量,一共有三种情况,要么全拍最左边的奶牛,如果左右连续异类奶牛的数量分别为 a a a b b b 的话,那么 s u m = s u m + m a x ( 0 , a − 1 ) sum = sum + max(0,a- 1) sum=sum+max(0,a1) s u m = s u m + m a x ( 0 , b − 1 ) sum = sum + max(0,b-1) sum=sum+max(0,b1) s u m = s u m + a ∗ b   ( a , b ≥ 1 ) sum = sum + a * b\ (a,b \geq1) sum=sum+ab (a,b1) 然后按照公式求解即可。具体细节见代码。

题目描述:

Farmer John 最近购入了 N 头新的奶牛,每头奶牛的品种是更赛牛(Guernsey)或荷斯坦牛(Holstein)之一。

奶牛目前排成一排,Farmer John 想要为每个连续不少于三头奶牛的序列拍摄一张照片。

然而,他不想拍摄这样的照片,其中只有一头牛的品种是更赛牛,或者只有一头牛的品种是荷斯坦牛——他认为这头奇特的牛会
感到孤立和不自然。

在为每个连续不少于三头奶牛的序列拍摄了一张照片后,他把所有「孤独的」照片,即其中只有一头更赛牛或荷斯坦奶牛的照片,
都扔掉了。

给定奶牛的排列方式,请帮助 Farmer John 求出他会扔掉多少张孤独的照片。

如果两张照片以不同位置的奶牛开始或结束,则认为它们是不同的。

输入格式
输入的第一行包含 N。

输入的第二行包含一个长为 N 的字符串。如果队伍中的第 i 头奶牛是更赛牛,则字符串的第 i 个字符为 G。否则,第 i
 头奶牛是
 荷斯坦牛,该字符为 H。

输出格式
输出 Farmer John 会扔掉的孤独的照片数量。

数据范围
3≤N≤5×105
输入样例:
5
GHGHG
输出样例:
3
样例解释
这个例子中的每一个长为 3 的子串均恰好包含一头更赛牛或荷斯坦牛——所以这些子串表示孤独的照片,并会被 
Farmer John 扔掉。

所有更长的子串(GHGH、HGHG 和 GHGHG)都可以被接受。

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 5e5+10;

int n;
char str[N];
int l[N], r[N];

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n >> str + 1;
	
	int G = 0, H = 0;
	for(int i = 1; i <= n; ++i)
	{
		if(str[i] == 'G') l[i] = G, G = i;
		else l[i] = H, H = i;
	}
	
	G = n + 1, H = n + 1;
	for(int i = n; i >= 1; --i)
	{
		if(str[i] == 'G') r[i] = G, G = i;
		else r[i] = H, H = i;
	}
	
	LL sum = 0;
	for(int i = 1; i <= n; ++i)
	{
		int a = i - l[i] - 1, b = r[i] - i - 1;
// 		cout << a << " " << b << endl;
		sum += max(0, a-1);
		sum += max(0, b-1);
		if(a > 0 && b > 0) sum += (LL)(a) * (b);
	}
	
	cout << sum << endl;
	
	return 0;
}

三、字串分值

标签:贡献法

思路:思路跟上一题是一样的,感觉几乎就是原题。总值就是每个字符能贡献的和加起来,每个字符能贡献的就是当前字符左边和右边有多少个连续且不跟自己一样的个数之积再求和即可。

题目描述:

对于一个字符串 S,我们定义 S 的分值 f(S) 为 S 中恰好出现一次的字符个数。

例如 f(“aba”)=1,f(“abc”)=3, f(“aaa”)=0。

现在给定一个字符串 S[0…n−1](长度为 n),请你计算对于所有 S 的非空子串 S[i…j](0≤i≤j<n), f(S[i…j]) 的和是多少。

输入格式
输入一行包含一个由小写字母组成的字符串 S。

输出格式
输出一个整数表示答案。

数据范围
对于 20% 的评测用例,1≤n≤10;
对于 40% 的评测用例,1≤n≤100;
对于 50% 的评测用例,1≤n≤1000;
对于 60% 的评测用例,1≤n≤10000;
对于所有评测用例,1≤n≤100000。

输入样例:
ababc
输出样例:
21
样例说明
所有子串 f 值如下:

a     1
ab    2
aba   1
abab  0
ababc 1
 b    1
 ba   2
 bab  1
 babc 2
  a   1
  ab  2
  abc 3
   b  1
   bc 2
    c 1

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 1e5+10;

int n;
char str[N];
int l[N], r[N];
int last[26];

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> str + 1;
	n = strlen(str+1);
	
	for(int i = 1; i <= n; ++i)
	{
		int t = str[i] - 'a';
		l[i] = last[t];
		last[t] = i;
	}
	
	for(int i = 0; i < 26; ++i) last[i] = n + 1;
	for(int i = n; i >= 1; --i)
	{
		int t = str[i] - 'a';
		r[i] = last[t];
		last[t] = i;
	}
	
	LL res = 0;
	for(int i = 1; i <= n; ++i)
	{
		int a = i - l[i], b = r[i] - i;
		res += (LL)a * b;
	}
	
	cout << res << endl;
	
	return 0;
}

四、外卖店优先级

标签:模拟、枚举

思路:就是按照外卖店的编号进行遍历,把它们的订单时间都存起来,如果订单大于 3 3 3 的才会去判断(大于三的才有可能在优先级里),然后首先从小到大排个序,然后先算前一天前到上一次订单扣除掉优先级后的当前优先级,然后进行判断是否退出,然后再优先级加二,判断是否加入到优先缓存中,最后再在 T T T 时刻进行再次判断。具体细节见代码,核心就是要把所有可能的情况全部考虑到,再次判断是否满足所有的情况。

题目描述:

“饱了么”外卖系统中维护着 N 家外卖店,编号 1∼N。

每家外卖店都有一个优先级,初始时 (0 时刻) 优先级都为 0。

每经过 1 个时间单位,如果外卖店没有订单,则优先级会减少 1,最低减到 0;
而如果外卖店有订单,则优先级不减反加,每有一单优先级加 2。

如果某家外卖店某时刻优先级大于 5,则会被系统加入优先缓存中;
如果优先级小于等于 3,则会被清除出优先缓存。

给定 T 时刻以内的 M 条订单信息,请你计算 T 时刻时有多少外卖店在优先缓存中。

输入格式
第一行包含 3 个整数 N,M,T。

以下 M 行每行包含两个整数 ts 和 id,表示 ts 时刻编号 id 的外卖店收到一个订单。

输出格式
输出一个整数代表答案。

数据范围
1≤N,M,T≤105,1≤ts≤T,1≤id≤N
输入样例:
2 6 6
1 1
5 2
3 1
6 2
2 1
6 2
输出样例:
1
样例解释
6 时刻时,1 号店优先级降到 3,被移除出优先缓存;2 号店优先级升到 6,加入优先缓存。

所以是有 1 家店 (2 号) 在优先缓存中。

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 1e5+10;

int n, m, T;
vector<int> order[N];

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n >> m >> T;
	while(m--)
	{
		int t, id; cin >> t >> id;
		order[id].push_back(t);
	}
	
	int res = 0;
	for(int i = 1; i <= n; ++i)
	{
		if(order[i].size() >= 3)
		{
			sort(order[i].begin(), order[i].end());
			
			bool flag = false;
			int sum = 0, last = 0;
			for(int t: order[i])
			{
				int t2 = t - last;
				sum = max(0, sum - max(0, (t - last - 1)));
				if(sum <= 3) flag = false;
				sum += 2;
				if(sum > 5) flag = true;
				last = t;
			}
			sum = max(0, sum - max(0, (T - last - 1)));
			if(sum <= 3) flag = false;
			
			if(flag) res++;
		}
	}
	
	cout << res << endl;
	
	return 0;
}

五、日志统计

标签:模拟

思路:还是跟上一题的思路一样,遍历每个日志,如果该日志的点赞数大于等于 K K K ,在进行判断,首先将其时间从小到大排序,然后每 K K K 个时间进行枚举,如果这时间间隔在 D D D 之内那么这就算是个结果,输出然后退出即可。详情见代码。

题目描述:

小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。

其中每一行的格式是:

ts id  表示在 ts 时刻编号 id 的帖子收到一个”赞”。

现在小明想统计有哪些帖子曾经是”热帖”。

如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。

具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。

给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。

输入格式
第一行包含三个整数 N,D,K。

以下 N 行每行一条日志,包含两个整数 ts 和 id。

输出格式
按从小到大的顺序输出热帖 id。

每个 id 占一行。

数据范围
1≤K≤N≤105,0≤ts,id≤105,1≤D≤10000
输入样例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例:
1
3

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y second

const int N = 1e5+10;

int n, m, k;
vector<int> logs[N];

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n >> m >> k;
	while(n--)
	{
		int t, id; cin >> t >> id;
		logs[id].push_back(t);
	}


	for(int i = 0; i < N; ++i)
	{
		if(logs[i].size() >= k)
		{
			sort(logs[i].begin(), logs[i].end());
			
			for(int j = 0; j + k - 1 < logs[i].size(); ++j)
			{
				int z = j + k - 1;
				if(logs[i][z] - logs[i][j] + 1 <= m) 
				{
					cout << i << endl;
					break;
				}
			}
		}
	}
	
	return 0;
}
  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lijiachang030718

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值