JYUACM新生训练周赛1
小红的签到题
签到题,贪心
题意
一共有 \(𝑎\) 道题,一共有 \(𝑏\) 人参赛,所有人通过题目数量的总数为 \(𝑐\) 道。问最多有多少人ak?
ak的定义:所有题目全部通过。
思路
贪心的思想,让ak人数尽可能多,即是通过 \(a\) 道题的人数,所以所有通过题数除以比赛总题数下取整就是答案。
时间复杂度:\(O(1)\)
代码
#include <iostream>
using namespace std;
int main()
{
int a, b, c;
cin >> a >> b >> c;
cout << c / a;
return 0;
}
异或故事
思维、数学
题意
给定 \(t\) 组询问,每次询问都会给出一个正整数 \(a\) ,你需要在区间 \([1,10^9]\) 中找到两个正整数 \(b\) 和 \(c\) ,使得 \(b \oplus c = a\),\(\oplus\) 代表按位异或。
思路
构造题,首先考虑是否能找到规律或一种通解解决题目。容易想到先化两个未知数为一个未知数,假设固定 \(b = 1\),根据异或的性质 \(c = a \oplus b = a \oplus 1\),\(1\) 肯定符合规定区间,思考 \(a \oplus 1\) 是否会超出规定区间。
可以发现一个奇数异或 \(1\) 会减 \(1\),一个偶数异或 \(1\) 会加 \(1\),那么超出区间的数就可以明了了,只有 \(1\) 和 \(1000000000\),所以特殊处理这两个数,处理方法很多,我们选择最简单暴力的,写一个程序根据异或性质先算出这两个数的一种答案。
时间复杂度:\(O(1)\)
代码
#include <iostream>
using namespace std;
void solve()
{
int a;
cin >> a;
if (a == 1) cout << 2 << ' ' << 3 << '\n';
else if (a == 1000000000) cout << 512 << ' ' << 999999488 << '\n';
else cout << (a ^ 1) << ' ' << 1 << '\n';
}
int main()
{
int t;
cin >> t;
while (t --) solve();
return 0;
}
石子合并
思维、贪心、排序
题意
牛牛有 \(n\) 堆石子, 每堆石子有 \(a[i]\) 个, 牛牛每次可以选择相邻的两堆石子,然后拿走少的那一堆,得到的价值是两堆石子个数之和, 直到只剩下一堆石子。
如果拿走了第 \(i\) 堆石子, 那么第 \(i-1\) 堆和第 \(i+1\) 堆就会相邻。
牛牛想知道该怎么拿,才能使得到的价值最多。
思路
贪心的思想,每次拿到的价值要最大,必然是一直取最多的那个石堆和另一个石堆之和的价值,取走较少的石堆最终留下一个最多的一个石堆。
证明:假设存在两个非最多的石堆 \(a、b\) 和最多的石堆 \(c\) 则满足 \(a + c \ge a + b\) 且 \(b + c \ge b + a\),且一直拿走较少的石堆保留最多的石堆,石堆数大于二时一定可以保证最多的石堆与另一个石堆相邻。
时间复杂度:\(O(nlogn)\)
代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n;
int a[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
sort(a + 1, a + 1 + n);
ll res = 0;
for (int i = 1; i < n; i ++) res += a[i] + a[n];
cout << res;
return 0;
}
有了上面的分析,可以发现只要保留一个最大值且其他石堆都合并一个最大值,所以可以去掉排序,只需找出最大值处理答案。
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n;
ll a[N];
int main()
{
cin >> n;
ll res = 0, maxv = 0;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
maxv = max(maxv, a[i]);
res += a[i];
}
cout << res + (n - 2) * maxv;
return 0;
}
小红的口罩
贪心、优先队列、线段树、数列分块
题意
疫情来了,小红网购了 \(n\) 个口罩。
众所周知,戴口罩是很不舒服的。小红每个口罩戴一天的初始不舒适度为 \(a_i\) 。
小红有时候会将口罩重复使用(注:这是非常不卫生的!),每次重复使用时,该口罩的不舒适度会翻倍!
小红想知道,自己在不舒适度总和不超过 \(k\) 的情况下,最多能用现有的口罩度过多少天?
思路
贪心的思想,每次选的口罩是不舒适度最小的口罩,然后更新不舒适度翻倍。要实现自动排序且能快速得到最小值的数据结构,容易想到优先队列,先将所有口罩的不舒适度压入队列,队首为最小值,取走队首带一天后将队首*2再压入队列,直到不舒适度之和大于 \(k\)。
时间复杂度:\(O(nlogn)\)
另一个解法:线段树
通过上面的分析,重点是快速查找到整个数列中最小的不舒适度并更新,这个过程并不一定需要排序,我们可以选择一种数据结构维护区间的最小值和它的位置,这样我们就可以快速查找和修改,容易想到这个数据结构为线段树:单点修改+区间查找。
时间复杂度:\(O(n) + O(logn \times logk)\)
还有一个解法:数列分块
如果你不会STL、线段树,那么可以考虑数列分块。分块基于暴力的优化,此题最粗暴的思路是枚举 \(n\) 个数找出最小值,但这样会超时,那么我们可以将 \(n\) 个数分组,每组固定一个长度,同时维护每组的最小值和最小值的下标,那么此题就变成枚举组数,找出各组最小值的最小值,然后更新最小值翻倍,再更新最小值所在组的信息。
时间复杂度:\(O((m + \sqrt{n}) \times logk)\)
代码
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
typedef long long ll;
int n, k;
priority_queue<ll, vector<ll>, greater<ll>> pq;
int main()
{
cin >> n >> k;
while (n --)
{
int x;
cin >> x;
pq.push(x);
}
ll sum = 0, res = 0;
while (sum <= k) {
ll t = pq.top();
pq.pop();
sum += t;
pq.push(t * 2);
res ++;
}
cout << res - 1;
return 0;
}
线段树解法
#include <iostream>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<ll, int> PLI;
const ll INF = 0x3f3f3f3f;
const int N = 1e5 + 10;
int n, k;
ll a[N];
struct node {
int l, r;
ll num;
int pos;
} tr[N * 4];
void push_up(int u)
{
if (tr[u].num > tr[u << 1].num) {
tr[u].num = tr[u << 1].num;
tr[u].pos = tr[u << 1].pos;
}
if (tr[u].num > tr[u << 1 | 1].num) {
tr[u].num = tr[u << 1 | 1].num;
tr[u].pos = tr[u << 1 | 1].pos;
}
}
void build(int u, int l, int r)
{
if (l == r) tr[u] = {l, r, a[l], l};
else {
int mid = l + r >> 1;
tr[u] = {l, r, INF};
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
push_up(u);
}
}
void modify(int u, int pos)
{
if (tr[u].l == tr[u].r && tr[u].l == pos) {
tr[u].num *= 2;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
tr[u].num = INF;
if (pos <= mid) modify(u << 1, pos);
if (pos > mid) modify(u << 1 | 1, pos);
push_up(u);
}
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i ++) cin >> a[i];
build(1, 1, n);
ll sum = 0, res = 0;
while (sum <= k)
{
sum += tr[1].num;
int pos = tr[1].pos;
res ++;
modify(1, pos);
}
cout << res - 1;
return 0;
}
数列分块
#include <iostream>
#include <cmath>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<ll, int> PLI;
typedef pair<int, int> PII;
const ll inf = 1e18;
const int N = 1e5 + 10;
int n, len, k, m;
ll a[N], id[N], minv;
PLI b[N];
PII hid[N];
int find()
{
int res = 0;
for (int i = 1; i <= m; i ++)
if (b[i].fi < minv) minv = b[i].fi, res = b[i].se;
return res;
}
void modify(int i)
{
int l = hid[i].fi, r = hid[i].se;
b[i] = {inf, i};
while (l <= r)
{
if (a[l] < b[i].fi) b[i].fi = a[l], b[i].se = l;
l ++;
}
}
int main()
{
cin >> n >> k;
len = sqrt(n);
int sd = 0, ed = 0;
for (int i = 1; i <= n; i ++)
{
cin >> a[i];
id[i] = (i - 1) / len + 1;
if (id[i] != m) {
if (sd) hid[m] = {sd, ed};
sd = i, ed = i;
}
else if (id[i] == m) ed = i;
m = id[i];
}
hid[m] = {sd, ed};
for (int i = 1; i <= m; i ++) b[i] = {inf, i};
for (int i = 1; i <= n; i ++)
{
int l = id[i];
if (b[l].fi > a[i]) b[l].fi = a[i], b[l].se = i;
}
ll sum = 0, res = 0;
while (sum <= k)
{
minv = inf;
int pos = find();
a[pos] *= 2;
modify(id[pos]);
sum += minv;
res ++;
}
cout << res - 1;
return 0;
}

浙公网安备 33010602011771号