CSP2023-J2参考解析

[CSP-J 2023] 小苹果

小 Y 的桌子上放着 \(n\) 个苹果从左到右排成一列,编号为从 \(1\)\(n\)

小苞是小 Y 的好朋友,每天她都会从中拿走一些苹果。

每天在拿的时候,小苞都是从左侧第 \(1\) 个苹果开始、每隔 \(2\) 个苹果拿走 \(1\) 个苹果。随后小苞会将剩下的苹果按原先的顺序重新排成一列。

小苞想知道,多少天能拿完所有的苹果,而编号为 \(n\) 的苹果是在第几天被拿走的?

分析
解法1:模拟过程,大概有50分,加上一些乱搞做法可以优化到80+分

解法2:正解,模拟一下样例,找找数学规律

问题1:所求的是天数,每3个看作一个周期,每次拿走的是 \(\lceil n/3 \rceil\)

问题2,找规律发现每次一定是当元素位序 x%3==1是才被拿走,而最开始的最后一个元素在每次拿走元素后要么被拿走,要么是新序列的最后一个,所以其位序不会对结构产生影响,只需要记录元素个数即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;

void brute_force() {
    int n; cin >> n;
    int a = 0, b = 0, t = n;
    vector<int> st(n + 1);
    for (int i = 1; i <= n; i++) st[i] = i;
    int cnt = 0;
    while (cnt < n) {
        int i = 1, p = 1;
        while (i <= n) {
            if (st[i] == 0) i++;
            else if (st[i] && p % 3 != 1) i++, p++;
            else if (st[i] && p % 3 == 1) {
                if (i == n) b = a + 1;
                st[i++] = 0, p++, cnt++;
            }
        }
        a++;
    }
    cout << a << " " << b << endl;
}
void solve() {
    int n; cin >> n;
    int a = 0, b = 0;
    while (n > 2) {
        a++;
        if (!b && n % 3 == 1) b = a;
        n -= ceil(n / 3.0);
    }
    a += n;
    if (b == 0) b = a;
    cout << a << " " << b;
}
int main() {
    freopen("apple.in", "r", stdin);
    freopen("apple.out", "w", stdout);
    solve();
    fclose(stdin), fclose(stdout);
    return 0;
}

[CSP-J 2023] 公路

小苞准备开着车沿着公路自驾。

公路上一共有 \(n\) 个站点,编号为从 \(1\)\(n\)。其中站点 \(i\) 与站点 \(i + 1\) 的距离为 \(v_i\) 公里。

公路上每个站点都可以加油,编号为 \(i\) 的站点一升油的价格为 \(a_i\) 元,且每个站点只出售整数升的油。

小苞想从站点 \(1\) 开车到站点 \(n\),一开始小苞在站点 \(1\) 且车的油箱是空的。已知车的油箱足够大,可以装下任意多的油,且每升油可以让车前进 \(d\) 公里。问小苞从站点 \(1\) 开到站点 \(n\),至少要花多少钱加油?

分析

贪心:每次购买当前价格最低的油。

具体步骤:

  1. 对于每个站点,维护一个 \([1,i]\) 的最小油价站点 \(id\),res[i] 记录第 i 个站点的购买量.
  2. 对于当前位置 i 到达下一个站点的距离 \(v[i]\),需要的油量 \(t=\lceil v[i]/id \rceil\).
  3. 可以跑的距离 为 \(t*d\),可能会跑到 \(i+1\) 站点后面,所以对 \(v[i+1] -= d*t - v[i]\),也可能跑到 \(i+2\) 站点后面,所以同时对 \(i+1, i+2\) 维护一个合理的大小(≥0)。
  4. 答案汇总, \(ans=\sum_{i=1}^{n-1} res[i]*a[i]\).
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
int n, d;
int v[N], a[N], res[N];
void solve() {
    scanf("%d%d", &n, &d);
    for (int i = 1; i < n; i++) scanf("%d", &v[i]);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    int id = 0;  // low_price_id
    for (int i = 1; i < n; i++) {
        if (i == 1 || a[id] > a[i]) id = i;
        int t = ceil(1.0 * v[i] / d);  // 需要油量
        res[id] += t;
        v[i + 1] -= d * t - v[i];
        if (v[i + 1] < 0) {
            int c = v[i + 1];
            v[i + 1] = 0;
            v[i + 2] += c;
        }
    }
    LL ans = 0;
    for (int i = 1; i < n; i++) ans += (LL)res[i] * a[i];
    cout << ans;
}
int main() {
    freopen("road.in", "r", stdin);
    freopen("road.out", "w", stdout);
    solve();
    fclose(stdin), fclose(stdout);
    return 0;
}

[CSP-J 2023] 一元二次方程

分析 一道简单的模拟题目,就是注意细节,由于是多组数据,不好骗分。

其实这个题目的描述仅仅是对问题进行了细化,主要注意一句话:分母不为负数。
对于后续的操作,我们发现输入的 a出现的位置多为分母,所以进行转换,如果方程系数中 \(a\) 为负数,则 \(a,b,c\) 同时乘上 \(-1\).

其余没有什么坑点,就是多种情况的罗列了,具体看代码。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}
void solve() {
    int t, m, a, b, c;
    cin >> t >> m;
    while (t--) {
        cin >> a >> b >> c;
        if(a < 0) a*=-1, b*=-1, c*=-1;
        int delta = b * b - 4 * a * c;
        if (delta < 0) cout << "NO" << endl;
        else {
            int n = delta;// 对 delta 质因数分解
            map<int, int> mp;
            for (int i = 2; i <= n; i++) {
                int p = 0;
                while (n && n % i == 0) n /= i, p++;
                if (p) mp[i] = p;
            }
            if (n) mp[n] = 1;
            int q1 = -b, q2 = delta > 0, r = delta > 0;
            for (auto u : mp) {
                int x = u.first, y = u.second;
                if (y & 1) r *= x;
                q2 *= pow(x, y / 2);
            }
            // cout<<"test: "<<q1<<" "<<q2<<" "<<r<<" "<<delta<<endl;
            int d1 = gcd(q1, 2 * a), d2 = gcd(q2, 2 * a);
            if (r <= 1) {
                int p = q1 + q2, q = 2 * a, d = gcd(p, q);
                p /= d, q /= d;
                if (q < 0) p *= -1, q *= -1;
                if (p == 0) { cout<<0<<endl; continue; }
                cout << p;
                if(q!=1) cout<<"/" << q;
                cout<< endl;
            } else {
                int p = q1, q = 2 * a;
                p /= d1, q /= d1;
                if (q < 0) p *= -1, q *= -1;
                if (p != 0) {
                    cout << p;
                    if (q != 1) cout << "/" << q;
                }
                int f = p;
                p = q2, q = 2 * a, p /= d2, q /= d2;
                if (q < 0) p *= -1, q *= -1;
                if (f != 0 && p > 0) cout << "+";
                else if (p == -1) cout << "-";

                if(abs(p) != 1) cout << p;
                if (p > 1) cout << "*";
                cout << "sqrt(" << r << ")";
                if (q != 1) cout << "/" << q;
                cout << endl;
            }
        }
    }
}
int main() {
    freopen("uqe.in", "r", stdin);
    freopen("uqe.out", "w", stdout);
    solve();
    fclose(stdin), fclose(stdout);
    return 0;
}

[CSP-J 2023] 旅游巴士

最短路的基础上多了条件

  1. 起点和终点时间为 k 的倍数.
  2. 通过i点的时间不能 < a[i].

分析

部分分分析

如果无解输出 -1,一般会出现一个点,不然这句话就白说了,直接输出 -1 就有 5分。
\(k=1\) 也就是任意时刻都可以到起点和终点。
\(a[i]=0\) 也就是任意时刻都可以通过i点。

满分解法

最短路+分层图
bfs,spfa,dijkstra

在实际测试中,spfa的效率通常会比dijkstra要高,当然它可能死 O(n)~O(nm).

点击查看代码
#include <bits/stdc++.h>
#include <type_traits>
#include <variant>
using namespace std;
#define PII pair<int, int>
typedef long long LL;
const int N = 1e4 + 10, K = 110, INF = 0x3f3f3f3f;
int n, m, k, h[N], idx, dis[N][K];
bool st[N][K];
struct T {
    int v, w, nx;
} g[N << 1];
void add(int u, int v, int w) {
    g[++idx] = {v, w, h[u]}, h[u] = idx;
}
int dijkstra() {
    memset(dis, 0x3f, sizeof dis);
    priority_queue<PII, vector<PII>, greater<PII>> q;
    dis[1][0] = 0, q.push({0, 1});
    while (q.size()) {
        auto it = q.top(); q.pop();
        int x = it.first % k, u = it.second;
        if (st[u][x]) continue;
        st[u][x] = 1;
        for (int i = h[u]; i; i = g[i].nx) {
            int v = g[i].v, w = g[i].w;
            int tv = max((w - x - 1 + k) / k * k + x, dis[u][x]);
            int& tt = dis[v][(x + 1) % k];
            if (tt > tv + 1) q.push({tt = tv + 1, v});
        }
    }
    return dis[n][0] == INF ? -1 : dis[n][0];
}
int spfa() {
    memset(dis, 0x3f, sizeof dis);
    queue<PII> q;
    dis[1][0] = 0, st[1][0] = 1, q.push({1, 0});
    while (q.size()) {
        auto it = q.front(); q.pop();
        int u = it.first, x = it.second;
        st[u][x] = 0;
        for (int i = h[u]; i; i = g[i].nx) {
            int v = g[i].v, w = g[i].w, y = (x + 1) % k;
            int tv = max((w - x - 1 + k) / k * k + x, dis[u][x]);
            int& tt = dis[v][y];
            if (tt > tv + 1) {
                tt = tv + 1;
                if (!st[v][y]) st[v][y] = 1, q.push({v, y});
            }
        }
    }
    return dis[n][0] == INF ? -1 : dis[n][0];
}
void solve() {
    cin >> n >> m >> k;
    int u, v, w;
    while (m--) cin >> u >> v >> w, add(u, v, w);
    cout << spfa() << endl;
    // cout << dijkstra()<<endl;
}
int main() {
    freopen("bus.in", "r", stdin);
    freopen("bus.out", "w", stdout);
    solve();
    fclose(stdin), fclose(stdout);
    return 0;
}

本套测试题暴露的几个值得分析的问题

  • T1,T4 很多 MLE,数组开大了,同学们要学习计算空间大小,不要盲开.
  • T1 有很多递归实现的,int无返回值.
  • T2 局部变量未初始化,O2优化后会出现问题.
  • T3 sqrt写成sprt, gcd写的暴力.
  • 文件保存位置不对,导致没有收取到选手文件
  • 文件输入输出写错
posted @ 2023-10-30 12:01  HelloHeBin  阅读(312)  评论(0编辑  收藏  举报