2020 团体程序设计天梯赛 个人题解 + 反思

据上一次参加天梯赛不到半年,但参加比赛的场景还历历在目,其中印象特别深刻的是在看洛希极限的时候大脑一片模糊,第一遍直接跳过了,直到最后20分钟才重新拾起来看,看了很久、不断在纸上写清楚了各种条件,最后卡时间一发过了。

现在觉得实际上并没有那么难,主要弱在在于对题目的分析和理解吧。

L2的口罩发放也印象十分深刻,细节很多的大模拟.

L1-1 嫑废话上代码 (5分)

直接输出即可

L1-066 猫是液体 (5 分)

体积公式大家应该都熟悉吧..

\[V = L · H · W \]

L1-067 洛希极限 (10 分)

阅读理解....,考虑精度 1e-6

void solve() {
    double b1, b2, s;
    cin >> b1 >> s >> b2;
    double ans = 0;
    if (s == 1) ans = b1 * 1.26;
    else
        ans = b1 * 2.455;
    cout << fixed << setprecision(2) << ans << " ";
    cout << ((ans - b2) <= 1e-6 ? "^_^\n" : "T_T\n");
}

L1-068 调和平均 (10 point(s))

由题意,调和平均定义为:\((\frac1{a_1} + \frac1{a_2}+\dots+\frac{1}{a_n})/n\)

最后再去倒数。

void solve() {
    double ans = 0.0;
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i) {
        double x;
        cin >> x, ans += 1.0 / x;
    }
    ans = n * 1.0 / ans;
    cout << fixed << setprecision(2) << ans;
}

L1-069 胎压监测 (15 分)

模拟3种情况即可

void solve() {
    int a[5] = {0}, Min, FZ, cnt = 0;
    int Max = -1, idx;
    for (int i = 1; i <= 4; ++i) {
        cin >> a[i];
        if (Max < a[i]) Max = a[i], idx = i;
    }
    cin >> Min >> FZ;
    vector<int> Idx;
    for (int i = 1; i <= 4; ++i) 
        if (abs(a[i] - Max) > FZ || a[i] < Min) cnt++, Idx.push_back(i);
    
    if (cnt == 0) cout << "Normal";
    else if (cnt == 1)
        cout << "Warning: please check #" << Idx[0] << "!";
    else
        cout << "Warning: please check all the tires!";
}

L1-070 吃火锅 (15 分)

使用 String 的 find 函数就能快速通过

注意一下输出要求,不小心看错wa几发..

void solve() {
    string s;
    vector<int> Idx;
    int cnt = 0;
    while (getline(cin, s)) {
        if (s == ".") break;
        cnt++;
        if (s.find("chi1 huo3 guo1") < s.size()) Idx.push_back(cnt);
    }
    cout << cnt << "\n";
    if (Idx.size() == 0) cout << "-_-#";
    else
        cout << Idx[0] << " " << Idx.size();
}

L1-071 前世档案 (20 point(s))

推理+二叉树(左节点 2*root + 1 右节点 2* root + 2)

void solve() {
    int n, m;
    cin >> n >> m;
    int t = pow(2, n);
    for (int i = 0; i < m; i++) {
        string s;
        cin >> s;
        int root = 1;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == 'y') root = root * 2;
            else
                root = root * 2 + 1;
        }
        cout << root - t + 1 << '\n';
    }
}

L1-072 刮刮彩票 (20 point(s))

L1里最麻烦的模拟题了,这里就没放代码了。

跟着题意来即可

L2,L3级别的题这里只写个人需要记录的

L2-033 简单计算器 (25 point(s))

L2-034 口罩发放 (25 point(s))

这个题的难点不在于题目,而在于你的读题水平,记住这个题的几个约束条件。1、身份证号必须是18位。2、同一个人在第i天申请成功,则需要等到i + P + 1天才能再次申请。3、每天有S个名额,直到发完为止。4、排序首先按照提交的时间排序,如果时间相同的话,那么按照次序,也就是当天提交时候的顺序。5、答案1记录每天发放的口罩的人的姓名和身份号,答案2记录所有的身体状况位1的人,按照顺序记录,而且只记录一次。所以这个要在排序之前就记录下来每天的身体状况为1的人

#include <bits/stdc++.h>
using namespace std;
const int N = 1100;
struct edge {

    string name;
    string id;
    int heal;
    int times;
    int ci;
};
int D, P, S, T;
bool check(string id) {
    if (id.size() != 18) {
        return false;
    }

    for (int i = 0; i < 18; i++) {
        if (id[i] < '0' || id[i] > '9') {
            return false;
        }
    }
    return true;
}

bool cmp(edge e1, edge e2) {
    if (e1.times == e2.times) {
        return e1.ci < e2.ci;
    }
    return e1.times < e2.times;
}
map<string, int> mp;
set<string> st;
vector<pair<string, string>> res;
int main() {
    cin >> D >> P;

    for (int i = 1; i <= D; i++) {
        cin >> T >> S;
        vector<edge> v;
        for (int j = 0; j < T; j++) {
            edge e;
            int a, b;
            cin >> e.name >> e.id >> e.heal;
            scanf("%d:%d", &a, &b);
            e.times = a * 60 + b;
            e.ci    = j;
            if (check(e.id)) {
                if (e.heal == 1) {
                    if (!st.count(e.id)) {
                        st.insert(e.id);
                        res.push_back({e.name, e.id});
                    }
                }
                v.push_back(e);
            }
        }
        sort(v.begin(), v.end(), cmp);

        vector<pair<string, string>> ans;
        for (auto &x : v) {
            string id = x.id;
            if (S == 0) break;
            if (mp[id] <= i) {
                mp[id] = i + P + 1;
                ans.push_back({x.name, x.id});
                S--;
            }
        }

        for (auto &x : ans) {
            cout << x.first << ' ' << x.second << endl;
        }
    }
    for (auto &x : res) {
        cout << x.first << ' ' << x.second << endl;
    }
    return 0;
}

L2-035 完全二叉树的层序遍历 (25 point(s))

对于递归和递推问题的求解一般有两部:第一步是分解子问题 ,第二部是确定问题的边界。
首先分解子问题:假设现在只有三个点:父节点,左孩子和右孩子,对于这颗简单树的后序遍历为:左孩子->右孩子->父亲节点 ,那么建树的时候只用把它的顺序反过来,即父亲节点->左孩子->右孩子
然后确定问题的边界:
根据完全二叉树的定义,每一层的结点都是优先从左往右放置,我们可以用一维数组模拟树,有几个点就需要占用几个数组结点:

      1
   2    3
 4  5  6  7
8

然后递归建树 输出即可

// Murabito-B 21/04/21
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Node {
    int data;
    int l, r;
} node[40];
void dfs(int d) { // 递归建树
    if (node[d].l != 0) dfs(node[d].l);
    if (node[d].r != 0) dfs(node[d].r);
    cin >> node[d].data;
}
void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        if (i * 2 <= n) node[i].l = i * 2;
        if (i * 2 + 1 <= n) node[i].r = i * 2 + 1;
    }
    dfs(1);
    cout << node[1].data;
    for (int i = 2; i <= n; ++i) cout << " " << node[i].data;
    cout << "\n";
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    solve();
    return 0;
}

L2-036 网红点打卡攻略 (25 point(s))

  • 给你一个路径,判断这条路径是否是一个NP完全问题的解,如果是找出路径最短的那个解,而且还得记录有多少个正确的解
  • 这个题要判断给的点是否是n个不同的点就好了。1、p等于n。2、这p个点没有重复的点
#include <bits/stdc++.h>
using namespace std;
using ll    = long long;
const int N = 210;
int n, m;
int k, p;
int g[N][N];
int road[N];
ll id, ans = 1e9;
ll sum;
bool check() {
    sum = 0, sum += g[0][road[1]];
    for (int i = 1; i < p; ++i) {
        int x = road[i], y = road[i + 1];
        sum += g[x][y];
    }
    sum += g[road[p]][0]; // 回起点

    if (sum >= 1e9) return false;
    return true;
}
void solve() {
    cin >> n >> m;
    memset(g, 0x3f, sizeof(g));

    for (int i = 0, a, b, c; i < m; ++i) {
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    cin >> k;
    int cnt = 0;
    for (int i = 1; i <= k; ++i) {
        cin >> p;
        set<int> ss;
        for (int j = 1; j <= p; ++j) {
            cin >> road[j];
            ss.insert(road[j]);
        }

        if (p == n && ss.size() == n && check()) {
            if (ans > sum) ans = sum, id = i;
            cnt++;
        }
    }
    cout << cnt << "\n"
         << id << " " << ans << '\n';
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    solve();
    return 0;
}

L3-025 那就别担心了 (30 point(s))

骗分做法:

首先根据推理关系我们可以DFS搜索找到 \(A \to B\) 的道路条数

“逻辑自洽” 和 拓扑排序是一样的所以我们可以检查出度为 \(0\) 的个数

如果等于 \(1\) 说明存在“逻辑自洽”

此方法过 \(21\) 分,有两个测试点 WA了所以就有了下面另一个 DFS写法(虽然也TLE了最后一个点

// Murabito-B 21/04/21
#include <bits/stdc++.h>
using namespace std;
using ll       = long long;
const int MAXN = 550;
int N, M;
int e[MAXN][MAXN];
int A, B;
int cnt = 0;
struct node { // 检验入度和出度
    int in, to;
} dist[MAXN];
bool vis[MAXN];
void dfs(int x) {
    if (x == B) {
        cnt++;
        return;
    }
    for (int i = 1; i <= N; ++i) {
        if (vis[i] == false && e[x][i]) {
            vis[i] = true;
            dfs(i);
            vis[i] = false;
        }
    }
}
void solve() {
    cin >> N >> M;
    for (int i = 1, s1, s2; i <= M; ++i) {
        cin >> s1 >> s2;
        e[s1][s2] = 1;
        dist[s2].in++;
        dist[s1].to++;
    }
    cin >> A >> B;
    vis[A] = true;
    dfs(A);
    cout << cnt << " ";
    int ans = 0;
    for (int i = 1; i <= N; ++i)
        if (dist[i].to == 0) ans++;

    cout << (ans == 1 ? "Yes\n" : "No\n");
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    solve();
    return 0;
}

优化代码,得分:28分,卡最后一个点超时

// Murabito-B 21/04/21
#include <bits/stdc++.h>
using namespace std;
using ll    = long long;
const int N = 1e6 + 10;
vector<int> e[N];
int n, m;
int S, E;
int cnt;
bool f = true;
void dfs(int u) {
    if (u == E) {
        cnt++;
        return;
    }
    if (u != E)
        if (e[u].size() == 0) f = false;

    for (int i = 0; i < e[u].size(); ++i)
        dfs(e[u][i]);
}
void solve() {
    cin >> n >> m;
    while (m--) {
        int a, b;
        cin >> a >> b;
        e[a].push_back(b);
    }
    cin >> S >> E;
    dfs(S);
    cout << cnt << " " << (f ? "Yes\n" : "No\n");
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    solve();
    return 0;
}

AC解法

想了一下,既然会超时说明在其中做了很多重复计算,需要记忆化搜索,不能爆搜(蒟蒻习惯性暴力

AC 代码:

// Murabito-B 21/04/21
#include <bits/stdc++.h>
using namespace std;
using ll    = long long;
const int N = 1e6 + 10;
vector<int> e[N];
int n, m;
int S, E;
int cnt[N];
bool vis[N];
bool f = true;
int dfs(int u) {
    // 不是终点但也不难往下走了,即出度为0
    if (u != E)
        if (e[u].size() == 0) f = false;
    vis[u] = true;
    if (cnt[u]) return cnt[u];
    for (int i = 0; i < e[u].size(); ++i)
        cnt[u] += dfs(e[u][i]);
    return cnt[u];
}
void solve() {
    cin >> n >> m;
    while (m--) {
        int a, b;
        cin >> a >> b;
        e[a].push_back(b);
    }
    cin >> S >> E;
    cnt[E] = 1;
    dfs(S);
    cout << cnt[S] << " " << (f ? "Yes\n" : "No\n");
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    solve();
    return 0;
}
posted @ 2021-04-19 21:17  RioTian  阅读(26)  评论(1编辑  收藏