牛客小白月赛 61 题解

比赛题目通道

前言

以下内容转自牛客oj

首先,十分抱歉给大家带来了不好的比赛体验,下面是比赛中出的大锅。

锅 1:B 题是出题人在读 CSAPP 时想到的一道小模拟,但在题目描述时出了问题,应该是离 a1.a2a1.a2a1.a2 最近的偶数。

锅 2:C 题题面叙述不清,存在歧义,让选手做得很难受。

锅 3:由于出题人最近对红警比较上头,因此有了 D 题,本质上是模拟。但由于 “这模拟” 坑点过多,最终成了 “折磨你”,对小白极不友好。

这是出题人第一次出全网比赛,虽然赛前进行了重重检验,但赛时还是出了诸多问题,十分抱歉。

Problem A. 超市里扫货

顺序扫描所有货物,维护购物车的剩余容积。若购物车的剩余容积不足以装下当前货物,则新推一辆购物车。此外需要注意,由于 \(max(V)=2^{30}\),所以两个物品的体积相加可能超过\(INT\_MAX\)

时间复杂度:\(O(n)\)

#include <bits/stdc++.h>
using namespace std;

void work()
{
    int n, v; scanf("%d %d", &n, &v);
    int rem = v, cnt = 1;
    for (int i = 1, a; i <= n; ++i) {
        scanf("%d", &a);
        if (rem >= a) rem -= a;
        else ++cnt, rem = v - a;
    }
    printf("%d\n", cnt);
}

int main()
{
    work();
    return 0;
}

Problem B. 柜台结账

分类讨论一下所有情况,我们可以发现:

支付的金额与原价格相比不变的情况只有一种,即 \(a_2=0\)

支付的金额与原价格相比多了的情况有两种:

​ ① \(0.a_2>0.5\)

​ ② \(0.a_2=0.5\)\(a_1\)​ 为奇数

其余情况则说明支付的金额与原价格相比少了。

时间复杂度:\(O(|a_2|)\)

#include <bits/stdc++.h>
using namespace std;

void work()
{
    string a1, a2; cin >> a1 >> a2;
    
    auto cmp = [](const string &s) {
        if (s[0] > '5') return 1;
        if (s[0] < '5') return -1;
        for (size_t i = 1; i < s.size(); ++i)
            if (s[i] != '0') return 1;
        return 0;
    };
    
    if (a2 == "0") cout << "PLMM\n";
    else if (cmp(a2) == 1 || (cmp(a2) == 0 && (a1.back()&1))) cout << "Happy birthday to MFGG\n";
    else cout << "Happy birthday to YXGG\n";
}

int main()
{
    work();
    return 0;
}

Problem C. 小喵觅食

以 PLMM 和小喵的初始位置分别做 BFS,距离数组分别记为 \(dis[0][i][j]\)\(dis[1][i][j]\),需要注意的是 PLMM 进行 BFS 时有活动范围 \(r_1\)​ 的限制,而小喵进行 BFS 时没有活动范围 \(r_2\)​ 的限制。

记小喵的初始位置为 \((mx,my)\),枚举 PLMM 的终止位置 \((i,j)\) 满足 \(|i-mx|+|j-my|\leq r_2\)​,答案即为 \(min(dis[0][i][j] + dis[1][i][j])\)

时间复杂度:\(O(nm)\)

#include <bits/stdc++.h>
using namespace std;

const int M = (int)1e3;
const int inf = 0x3f3f3f3f;

int n, m, r1, r2;
char s[M + 5][M + 5];
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
int dis[2][M + 5][M + 5];

void bfs(int u, int x, int y, int r)
{
    queue<pair<int, int>> q;
    dis[u][x][y] = 0;
    q.push({x, y});
    while (!q.empty()) {
        auto [ux, uy] = q.front(); q.pop();
        for (int i = 0; i < 4; ++i) {
            int vx = ux + dx[i], vy = uy + dy[i];
            if (vx < 1 || vx > n || vy < 1 || vy > m || s[vx][vy] == '*' || dis[u][vx][vy] < inf) continue;
            if ((int)abs(vx - x) + (int)abs(vy - y) > r) continue;
            dis[u][vx][vy] = dis[u][ux][uy] + 1;
            q.push({vx, vy});
        }
    }
}

void work()
{
    scanf("%d %d %d %d", &n, &m, &r1, &r2);
    memset(dis, inf, sizeof dis);
    for (int i = 1; i <= n; ++i) scanf("%s", s[i] + 1);
    int mx = 0, my = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            if (s[i][j] == 'P') bfs(0, i, j, r1);
            else if (s[i][j] == 'M') bfs(1, i, j, inf), mx = i, my = j;
    int mi = inf;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            if ((int)abs(i - mx) + (int)abs(j - my) <= r2)
                mi = min(mi, dis[0][i][j] + dis[1][i][j]);
    printf("%d\n", mi == inf ? -1 : mi);
}

int main()
{
    work();
    return 0;
}

Problem D. 石油大亨

模拟题,做法因人而异,下面是出题人的做法。

首先,若 \(s<ec\),则无解输出 \(-1\)

\(i\) 遍历 \(1,2,\cdots,n\) 枚举工程师,\(cur\_time_i\)​ 表示第 \(i\) 个工程师开始训练的时间,答案即为 \(cur\_time_n+et+t[n]\)。通过维护当前金额和油田个数,使用 \(cur\_time_{i-1}\)​ 计算出 \(cur\_time_i\)​。

具体细节请见代码。

PS:由于金额可能会爆long long,因此需要特判一下。

时间复杂度:\(O(n)\)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int M = (int)1e5;
const ll linf = 0x3f3f3f3f3f3f3f3f;

int n, ec, et, p; ll s;
int t[M + 5];

void work()
{
	scanf("%d %d %d %d %lld", &n, &ec, &et, &p, &s);
	for (int i = 1; i <= n; ++i) scanf("%d", &t[i]);
	if (s < ec) return printf("-1\n"), void();
	queue<ll> q; //存储工程师占领油田的时间
	ll cur_time = 0;
	for (int i = 1, oil_cnt = 0; i <= n; ++i) {
		ll next_time = (i == 1 ? 0 : cur_time + et); //下一次开始训练工程师的最小时间戳
		while (!q.empty() && q.front() <= next_time) { //计算从当前到next_time的金额收益
			if(s < linf) s += oil_cnt * (q.front() - cur_time) * p;
			cur_time = q.front();
			q.pop();
			++oil_cnt;
		}
		if (cur_time < next_time) {
			if(s < linf) s += oil_cnt * (next_time - cur_time) * p;
			cur_time = next_time;
		}
		if (s < ec) { //金额不足以训练工程师
			while (!q.empty() && s + oil_cnt * (q.front() - cur_time) * p < ec) {
				s += oil_cnt * (q.front() - cur_time) * p;
				cur_time = q.front();
				q.pop();
				++oil_cnt;
			}
			ll wait_time = (ec - s + (ll)oil_cnt * p - 1) / oil_cnt / p; //需要等待wait_time的时间
			s += oil_cnt * wait_time * p;
			cur_time += wait_time;
		}
		s -= ec;
		q.push(cur_time + et + t[i]);
	}
	printf("%lld\n", cur_time + et + t[n]);
}

int main()
{
	work();
	return 0;
}

Problem E. 排队

\(a_i \not= a_j\)​,则考虑 \((a_i,a_j)\) 对答案的贡献。

由于是随机排列,所以有 \(\frac{n!}{2}\)​ 种排列使得 \((a_i,a_j)\) 对答案产生 \(1\) 的贡献。

因此答案为 \(\frac{n!}{2}\sum_{1 \leq i < j \leq n}{[a_i \not= a_j]}\)

\(cnt[a_i]\) 表示 \(a_i\)​ 在序列 \(a\) 中的出现次数,可以使用 \(cnt[]\) 数组 \(O(n)\) 求出 \(\sum_{1\leq i < j \leq n}[a_i \not= a_j]\)

时间复杂度:\(O(n)\)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int M = (int)1e5;
const int mod = (int)1e9 + 7;

int cnt[M + 5];

void work()
{
    int n; scanf("%d", &n);
    int ans = 0;
    for (int i = 1, a; i <= n; ++i) {
        scanf("%d", &a);
        (ans += i - ++cnt[a]) %= mod;
    }
    for (int i = 3; i <= n; ++i) ans = (ll)ans * i % mod;
    printf("%d\n", ans);
}

int main()
{
    work();
    return 0;
}

Problem F. 选座椅

容易想到,如果区间 \([l,r]\) 可以满足 \(m\) 个条件,那么区间 \([l,r+1]\) 也一定可以满足 \(m\) 个条件。

对区间 \([l,r]\) 进行尺取,维护每个位置被使用的情况和满足的条件个数。对于每个 \(l\) 求出满足 \(m\) 个条件的最小的 \(r\),使用差分数组求出不考虑顺序的方案数,再乘以阶乘即为答案。

时间复杂度:\(O(n+m)\)

#include <bits/stdc++.h>
using namespace std;

const int M = (int)1e5;
const int mod = (int)1e9 + 7;

int n, m;
vector<int> v[M + 5];
int cnt[M + 5], tot;
int d[M + 5];

void work()
{
    scanf("%d %d", &n, &m);
    for(int _ = 0; _ < 3; ++_)
        for(int i = 1, a; i <= m; ++i) {
            scanf("%d", &a);
            v[a].push_back(i);
        }
    auto add = [&](int p) {
        for(const int& x: v[p])
            if(++cnt[x] == 1)
                ++tot;
    };
    
    auto del = [&](int p) {
        for(const int &x: v[p])
            if(--cnt[x] == 0)
                --tot;
    };
    
    for(int l = 1, r = 0; l <= n; ++l) {
        while(r + 1 <= n && tot < m) add(++r);
        if(tot == m) ++d[r - l + 1], --d[n - l + 2];
        del(l);
    }
    
    for(int i = 1, fac = 1; i <= n; ++i) {
        fac = (long long)fac * i % mod;
        d[i] += d[i - 1];
        printf("%lld%c", (long long)fac * d[i] % mod, " \n"[i == n]);
    }
}

int main()
{
    work();
    return 0;
}
posted @ 2022-11-19 18:01  SZBR_yzh  阅读(106)  评论(0编辑  收藏  举报