Codeforces Round 799 (Div. 4)D~H

Codeforces Round 799 (Div. 4)D~H

D. The Clock(模拟)

题意

给初始时间和一个间隔时间,每一次间隔时间都会看一次表,直到出现重复

思路

模拟,字符串转数字,数字转字符串。如果HH大于24,对24取模。如果MM大于60,那就HH+=MM/60,然后MM对60取模。每次判断是不是回文串就行

代码

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;

bool check(string s) {
	for (int i = 0; i < s.length() / 2; i++) {
		if (s[i] != s[s.length() - i - 1])return false;
	}
	return true;
}

void solve() {
	string s; cin >> s;
	int x; cin >> x;
	int nh = x / 60;
	int nm = x % 60;
	int h, m;
	string s1 = s.substr(0, 2);
	string s2 = s.substr(3, 2);
	h = stoi(s1); m = stoi(s2);
	s1 = to_string(h);
	s2 = to_string(m);
	if (s1.length() == 1)s1 = "0" + s1;
	if (s2.length() == 1)s2 = "0" + s2;
	string t = s1 + s2;
	string S;
	int cnt = 0;
	do {
		h += nh;
		m += nm;
		h += m / 60;
		m %= 60;
		if (h >= 24) h %=24;
		s1 = to_string(h);
		s2 = to_string(m);
	
		if (s1.length() == 1)s1 = "0" + s1;
		if (s2.length() == 1)s2 = "0" + s2;
		S = s1 + s2;
		if (check(S))cnt++;
	} while (t != S);
	ans.push_back(cnt);
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	cin >> _;
	while (_--)	solve();
	for (auto x : ans)cout << x << endl;
	return 0;
}

E. Binary Deque(双指针、滑动窗口)

题意

给一个只包含1和0的数组,要从开头或者结尾删掉一些数字,使得数组总和为s

思路

双指针滑动窗口,维护区间,使得区间里面的和永远是s

l起始数组开头的下标,r初始是使得l~r的和为s的位置

然后枚举r,枚举r的过程中,如果区间和sum不合法,移动左指针l,每次尝试更新答案,应该是O(n)

代码

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;

void solve() {
	// 双指针滑动窗口
	int n, s; cin >> n >> s;
	vector<int>a(n + 1),sumA(n+1);
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		sumA[i] = sumA[i - 1] + a[i];
	}
	if (sumA[n] < s) {
		ans.push_back(-1);
		return;
	}
	int l = 1, r = 1;
	while (sumA[r] - sumA[l - 1] < s) {
		r++;
	}
	int cost = n - r + l - 1;
	for (; r <= n; r++) {
		while (sumA[r] - sumA[l - 1] > s) {
			l++;
		}
		cost = min(cost, n - r + l - 1);
	}
	ans.push_back(cost);
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	cin >> _;
	while (_--)	solve();
	for (auto x : ans)cout << x << endl;
	return 0;
}

F. 3SUM(枚举)

题意

给一个数组,找三个不同的位置,使得ai+aj+ak以数字3结尾

思路

因为要最后一位数字是3,那取的那三个数字也只要看最后一位就行。记录数组中,每个数字(0~9)作为最后一位出现的次数。现在要从个位数里面找三个数字加起来是以3结尾的,三个个位数加起来以三结尾的结果只能是3、13、23。从0~9枚举第一个数字i,然后从0~9枚举第二个数字j,最后k就不枚举了,直接算。假设结果为3,算一个k出来,假设结果为13,算一个k出来,假设结果为23,也算一个k出来。判断k是否属于[0,9],再在原数字里面看以k结尾的数字还够不够。

代码

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<string>ans;

void solve() {
	int n; cin >> n;
	int cnt[10] = { 0 };
	for (int i = 0, a; i < n; i++) {
		cin >> a;
		cnt[a % 10]++;
	}
	for (int i = 0; i <= 9; i++) {
		if (!cnt[i])continue;
		cnt[i]--;
		for (int j = 0; j <= 9; j++) {
			if (!cnt[j])continue;
			cnt[j]--;
			int k = 23 - (i + j);
			if (0 <= k && k <= 9 && cnt[k]) {
				ans.push_back("YES");
				return;
			}
			k = 13 - (i + j);
			if (0 <= k && k <= 9 && cnt[k]) {
				ans.push_back("YES");
				return;
			}
			k = 3 - (i + j);
			if (0 <= k && k <= 9 && cnt[k]) {
				ans.push_back("YES");
				return;
			}
			cnt[j]++;
		}
		cnt[i]++;
	}
	ans.push_back("NO");
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	cin >> _;
	while (_--)	solve();
	for (auto x : ans)cout << x << endl;
	return 0;
}

G. 2^Sort(数学,前缀和)

题意

给一个数组,求一个满足一些条件的,长度为k+1的子数组的数量

这个子数组会把原数组里面的数,都乘2的某个次方。并且乘完之后要前一个数小于后一个数。

思路

观察一下发现,比如20 * ai < 21 * ai+1,把两边都除2,其实就是ai < 2 * ai+1,后面每一个位置的前后关系都是这样。也就是原数组里面也满足这样的ai < 2 * ai+1关系,就可以了

前缀和cnt数组,记录到i位置,最长可以到cnt[i]的满足条件的子数字

计算完cnt数组之后,数一下有多少个位置,大于等于k+1

代码

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;

void solve() {
	int n, k; cin >> n >> k;
	vector<int>a(n + 1), cnt(n + 1);
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		if ((a[i] << 1) > a[i - 1]) {
			cnt[i] = cnt[i - 1] + 1;
		}
		else {
			cnt[i] = 1;
		}
	}

	long long res = 0;
	for (int i = 1; i <= n; i++) {
		if (cnt[i] >= k + 1) {
			res++;
		}
	}

	ans.push_back(res);

	//for (int i = 1; i <= n; i++) {
	//	cout << cnt[i] << " ";
	//}
	//cout << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	cin >> _;
	while (_--)	solve();
	for (auto x : ans)cout << x << endl;
	return 0;
}

H. Gambling(前缀和)

题意

知道每一轮获奖的数字是什么,但是只准备在某个区间,一直选一个数字。问选哪个数字收益,在哪个区间选,收益最大

思路

这题也是看了别人的想法的,直接orz了这思路

很显然就可以想到是要求某个数字,在某个区间,这个数字出现的次数,比其他数字出现的总次数大。大多少。最大可以大多少。

这时候有一个方法就是,对于每一个数字x,都将原数组转化,把x看作1,其他数字看作-1。这时候再求前缀和数组sum,就可以知道这个数字x,在哪个[l,r]区间上的答案最大。

但是又可以对这个sum数组优化,就是搞一个map<int,vector<int>>,里面记录某个数字x的所有出现过的位置的下标。

sum前缀和数组就转化为sum[i] = sum[i-1] - (v[i] - v[i-1] - 1) + 1。

其中v就是记录了x所有出现位置的数组

减去(v[i] - v[i-1] - 1)就是说,v[i]是当前x在原数组中的位置,v[i-1]是x在原数组中上一次出现的位置,而这中间,就都看作是-1了。

最后+1是把当前v[i]这个位置的1给加上

其他的在代码里面说

代码

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;

void solve() {
	int n; cin >> n;
	map<int, vector<int>>mp;
	vector<int>a(n + 1);
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		mp[a[i]].push_back(i);
	}

	int res = 1, resNum = a[1], l = 1, r = 1;

	// x是假设的答案,v是x对应原数组的下标数组
	for (auto [x, v] : mp) {
		vector<int>sum(v.size());
		// 针对x做一个前缀和数组,把x看作1,其他数看作-1
		// 从x第一次出现的位置作为前缀和数组的起点,因为如果要选x,不可能在x第一次出现的位置之前开始赌
		sum[0] = 1;
		for (int i = 1; i < sum.size(); i++) {
			sum[i] = sum[i - 1] - (v[i] - v[i - 1] - 1) + 1;
		}
		// 每个区间的贡献是sum[r] - sum[l] + 1
		// 也就是要让sum[r]尽量大(遍历所有x出现的位置)
		// 要让sum[l]尽可能小,如果sum[r]已经遍历过了,而且比sum[l]小,那么l就可以更新到r
		int i = 0;
		// 因为前缀和数组是直接把所有x的下标挑出来做的前缀和数组
		// 所以前缀和数组的每一个位置,都在原数组中对应了x的位置
		// 每一个l和r,都是以x为左右端点的区间
		for (int j = 0; j < v.size(); j++) {
			int temp = sum[j] - sum[i] + 1;		// sum[i]包含了自己的v[i],但是减掉了,加回1
			if (temp > res) {					// 更新答案 
				res = temp;
				resNum = x;
				l = v[i];
				r = v[j];
			}
			if (sum[i] > sum[j])i = j;			// 让左端点的前缀和更小
		}
	}
	cout << resNum << " " << l << " " << r << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	cin >> _;
	while (_--)	solve();
	for (auto x : ans)cout << x << endl;
	return 0;
}
posted @ 2025-04-30 12:53  zombieee  阅读(36)  评论(0)    收藏  举报