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;
}
posted @ 2024-12-04 23:59  Natural_TLP  阅读(68)  评论(0)    收藏  举报