2022江西省大学生程序设计竞赛正式赛

酶和ATP 2022年10月24日 1,098次浏览

比赛链接:https://ac.nowcoder.com/acm/contest/43898

作为大二的同学,第一次参加江西省赛,运气还不错,最后做出三题获得了二等奖,感谢我的队友在赛时的一起努力,还有志愿者为我们的无私奉献,特别感谢C老师为我们的操劳,周日一早就来机房为我们做准备,还有准备的午饭非常好吃,竟然有四个菜(我平时都没这么丰盛!!!
可惜的是这次的举办方为江西省教育厅,不是ICPC组委会,不然就是银牌爷了哈哈。

L Thunder God ’s Hammer

签到。
开赛时一刷新网页,竟然有人几秒钟就过题,于是我直接打开L,题都没看完就开始#include <iostream>,2min就过了哈哈,交完发现28名,实在是手速题。

最后省赛榜单公布,如果三分钟之内过了L,竟然可以拿一个三等奖(难道是因为江西省太菜了x

#include <iostream>
using namespace std;

int n;

int main() {
    cin >> n;

    for (int i = 1; i <= n; i++) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        cout << a * b * c * d << endl;
    }

    return 0;
}

A Special Adjustment Method

题意:给三个非负数x,y,z,可以做n次操作(n可以等于0),每次操作k可以使一个数+2,两个数-1(操作过程中数要非负),求$x2+y2+z^2$的最大值。

WA了六发的神题,导致我们罚时爆炸,我们一开始发现要让小的数尽量小,大的数尽量大,然后直接排序后把最小的数减小为0,最大的数增加很多个2,交了一发WA。
然后X和Y同学灵机一动,发现最小的数如果先加大一点,加大到和中间的数差不多大,再一起减小,这样最大的数会更大,于是写了一发模拟,又WA。
然后X同学又发现,只要两个小的数相差是三的倍数,那么就可以一起减到0,如果不是就会减成01,写了一发又WA,百思不得其解。

于是我们换题写B。。。
B写不出来,又换回A。

通过智慧样例,我们把条件改成if (a[1] == a[2] || ((a[1] - a[0]) % 3 == 0)),结果又WA!
最后我发现可以if (((a[2] - a[1]) % 3 == 0) || ((a[1] - a[0]) % 3 == 0)),然后后WA,最后我乱打了一个测试样例1 9 10发现如果最小的和最大的相差3的倍数,也可以达成0 0的结果,提交了一发终于AC!

#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;

int main() {
    int t;
    cin >> t;
    for (int i = 1; i <= t; i++) {
        long long a[3];
        cin >> a[0] >> a[1] >> a[2];
        sort(a, a + 3);
        if (((a[2] - a[0]) % 3 == 0) || ((a[2] - a[1]) % 3 == 0) ||
            ((a[1] - a[0]) % 3 == 0))
            cout << (a[0] + a[1] + a[2]) * (a[0] + a[1] + a[2]) << endl;
        else
            cout << (a[0] + a[1] + a[2] - 1) * (a[0] + a[1] + a[2] - 1) + 1
                 << endl;
    }
    return 0;
}

B 01string

题意:给一个序列s0由0和1组成。s1比s0少最后一位,并且其中第i位上的值为s0上的max(i,i+1),以此类推s2,s3...。给序列s0和数字k,求s0,s1,s2....sk的所有序列中1的总个数。

很奇怪的题目,我的队友写了两个小时等差数列还没过,他们给我解释我也听不懂,于是我去吃中饭。我还以为这个思路有问题,没想到最后题解出来,和他们的思路适合是相匹配的,但是我还是看不懂呜呜。

说说我的AC思路,对于序列中的每一个字母,我们用ap(appeareance)来表示它第一次出现1的时间,如果在是s0时就是1了,那我就就记录为0,如果在s1的时候出现1,那就记录为1。再用dis(disappear)来记录这个数字消失的时间,因为从s0-s1会丢失最后一个字母,s1-s2又会丢失最后一个字母,如果这个数字是在s1的时候消失的(它是最后一个字母),我们就记录为0。

对于每一个序列的字母,要求他们的ap,我们只需要记录它距离它右边最近的一个1的距离即可(因为它只能被右边的1更新)。要求他们的dis,我们只需要记录它离字符串末尾的距离即可。

求出每个序列的ap和dis,要求k时1的总数,就只需要遍历一遍序列,如果出现(ap)在k之前,那就加上dis-ap,如果k在ap后,那就不用管,这样只需要遍历一遍序列就可以求解出答案了。

#include <algorithm>
#include <cstring>
#include <iostream>
#define int long long
using namespace std;

signed main() {
    ios::sync_with_stdio(0), cin.tie(0);
    int t;
    cin >> t;
    for (int i = 0; i < t; i++) {
        int n, k;
        cin >> n >> k;
        string a;
        cin >> a;
        int last1 = 0x3f3f3f3f;
        int ap, dis;
        int cnt = 0;
        for (int j = n - 1; j >= 0; j--) {
            if (a[j] == '0')
                ap = last1 - j;
            else
                last1 = j, ap = 0;
            dis = n - j;
            // cout << ap << ' ' << dis << endl;
            if (ap < k) {
                if (dis > k)
                    cnt += k - ap + 1;
                else
                    cnt += dis - ap;
            }
            if (ap == k) cnt++;
            // cout << "cnt=" << cnt << endl;
        }
        cout << cnt << endl;
    }
    return 0;
}

C Graphic Game

给一个 2×𝑛 个点,𝑚 条边,没有重边和自环的无向图,每次可以选择两个度数相等的点删除,求删完所有点的方案

赛时是手写了一个find函数,用来找两个最大的点,

度数的可能取值有 1,…,𝑚,而点数 2×𝑛>𝑚 个,即一个无向图最少存在两个点的度数相等,删除两个点后仍然是一个无向图,即该问题一定有解,先存下每个度数对应的节点,考虑从最大的度数开始删除两个数,用 𝑠𝑒𝑡 维护即可,为什么这样一定能删完?即现在要证明不存在这样一种情况:删除当前度数为 𝑡 的两个数后,所有度数 𝑥<=𝑡 的节点个数都不超过 1,但是可能存在度数 𝑥>𝑡 的节点个数超过 1,可以发现这样的情况很难构造出来,因为对于一个度数 𝑥>𝑡 的节点来说,说明会有 𝑥 条边由该节点连向其他节点,假设如果其他点的度数都 ≤𝑡,而有 𝑥 个这样的点,说明至少存在两个度数相等的点,假设不成立,故与该节点连的点中至少存在一个点的度数 >𝑡,即我们的那种要证明的不存在的情况说明:度数大的点至少也会跟度数大的点连边,且所有其他度数小的节点都不存在相等度数的节点,这样的情况更加难以构造。
source:https://www.cnblogs.com/zyyun/p/16819062.html

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

// 快读模版
template <typename T>
void inline read(T &x) {
    int f = 1;
    x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-') f = -1;
        s = getchar();
    }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N = 2e6 + 10, M = 1e6 + 10;
int h[N], e[N], ne[N], idx;
int n, m;
int d[N];
set<int> deg[M];  // 存度数

void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }

int main() {
    // 点 边
    read(n), read(m);
    memset(h, -1, sizeof h);

    // 存边
    for (int i = 1; i <= m; i++) {
        int a, b;
        read(a), read(b);
        add(a, b), add(b, a);
        d[a]++, d[b]++;
    }

    // 把点的度数插入set当中
    for (int i = 1; i <= 2 * n; i++) deg[d[i]].insert(i);

    // 循环 m 次,因为一个点最多有 m 度
    int t = m;
    while (t >= 0) {
        // 如果这个度数中,点的个数大于2
        if (deg[t].size() >= 2) {
            // 把前两个点取出来
            int x = *deg[t].begin();
            deg[t].erase(deg[t].begin());
            int y = *deg[t].begin();
            deg[t].erase(deg[t].begin());
            // 输出
            printf("%d %d\n", x, y);
            // 再删除与这个点相连的边
            d[x] = d[y] = 0;
            for (int i = h[x]; ~i; i = ne[i]) {
                int j = e[i];
                if (d[j] > 0) {
                    // 度数-1
                    deg[d[j]].erase(j);
                    d[j]--;
                    deg[d[j]].insert(j);
                }
            }
            for (int i = h[y]; ~i; i = ne[i]) {
                int j = e[i];
                if (d[j] > 0) {
                    deg[d[j]].erase(j);
                    d[j]--;
                    deg[d[j]].insert(j);
                }
            }
        } else {
            // 如果这个度数中,只有0或1个点
            // 那么这个度就没有遍历的必要
            t--;
        }
    }

    return 0;
}