2022年山大地纬杯”第十二届山东省ICPC大学生程序设计竞赛赛后总结

总结:

这场比赛前心里多上还是有点忐忑的, 虽然我觉得这场比赛能够拿到牌子, 但是鉴于我们队之前参加训练赛时总是会被一道题卡住并在赛后突然顿悟ac的情况, 我怕我们这次比赛也会因为卡在一道关键性的题目上而最终一无所获,事实上比赛时差点就这样了。 我们AC的三道题目中其中有两道题都是因为考虑不周或粗心大意而耗费了很长的时间才做出来。 A题我们当时没有考虑到特判的情况以至于久久未能过题, 好在最终能够反应过来找到特判情况最后AC过了; 另一个卡的H题也是,我们一直没有留意到我们的数据范围开小了, 以至于看代码的中间过程看的差点怀疑人生, 不过也是万幸在比赛结束前发现了这个错误, 最终保住了牌子。 事实上当时无论A还是H, 我们的解题思路都是对的, 毕竟这两道题并不难, 但是就是因为粗心大意和思虑不周,让我们错失了更多解决其他问题的机会 (果然还是我们太菜了😭) 。不过虽然过程并不如意, 但是收获还是有的, 更重要的是在比赛过程中我们队的心态一直保持的很好, 没有因为遇到挫折而变得焦虑不安大脑短路 (思路一直很清晰, 解题过程觉得崩对, 但奈何眼神不太好🙁) , 所以不管怎么说, 这次比赛我们还是成功的。
好了, 下面我把我们的出的题的思路写一下。

A题:

题目链接

思路:

首先用sum 存 (1-n)全部整数的和, 然后求出sum-17的差。
如果(1-n)中有一个数 i 是负数, 即运算时对 i 进行减法操作, 那么此时(1-n)的和就是 sum - 2i, 即sum 减去原先的正 i 加上 改后的 -i。
这样的话, 要求出题目中的结果只需要把能够组成(sum-17)/ 2 的整数进行减法操作就行了, 当然, 如果(sum-17) / 2 为奇数, 那么我们可以把 1 单独拿出来用 1
(其他数的+,-运算)。
至于求那些用做减法的数, 我们可以从 n开始遍历, 让tmp = (sum-17)/ 2 , 如果tmp > i + 1, 那么 tmp -= i。(一定是tmp > i+1, 用来处理提出 1 这个数的情况)

代码:
#include <bits/stdc++.h>
#define _max(a, b) ((a) > (b) ? (a) : (b))
#define _min(a, b) ((a) < (b) ? (a) : (b))
#define fi(i, a, b) for(int i = a; i <= b; ++i)
#define fd(i, a, b) for(int i = a; i >= b; --i)
#define gc getchar
#define pc putchar
#define il inline
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
const int mode = 1e9+7;
const double eps = 1e-6;


int n;
int a[maxn];
int main() {
    // fi(n, 1, 50) {
        cin >> n;
        memset(a, 0, sizeof(a));
        ll sum = 0;
        ll tmp = 0;
        fi(i, 1, n) {
            sum += i;
        }
        // cout << sum << " *** " << endl;
        tmp = sum - 17;
        
        ll tmp1 = 0;
        if (tmp < 0) {
            if (n == 5) {
                cout << "1+3+2*4+5";
            }
            else if (n == 4) {
                cout << "(4+1)*3+2";
            }
            else 
                cout << -1 << endl;
            // continue;
        }
        else{
            bool bl = 0;
            if (tmp % 2) {
                cout << 1 << "*(";
                bl = 1;
            }
            else cout << 1;
            tmp /= 2;
            fd(i, n, 2) {
                if (!tmp) break;
                if (tmp >= i && tmp != i+1) {
                    tmp -= i;
                    a[i] = 1;
                    tmp1 += i;
                }
            }
            fi(i, 2, n) {
                if (a[i]) {
                    cout << "-";
                }
                else {
                    if (i > 2 || (i == 2 && !bl)) {
                        cout << "+";
                    }
                }
                cout << i;
            }
            if (bl ) cout << ")";
        }   
    //     cout << endl << sum << " - " << tmp1*2 << " = " << (sum-tmp1*2) << endl;
    //     cout << endl << tmp << " **** " << endl << endl;
        
    // }
    return 0;
}

H题:

题目链接

思路:

每次移动之后都把每个点按 x,y坐标排序, 然后遍历一遍,找到一段连续的区间里面每个点的 x 和 y 的坐标都相同, 然后, 这样的单段区间就是相遇的所有点, 一段区间的对数是n*(n-1)/ 2, 按这种方式遍历统计下就可以了。

代码:
#include <bits/stdc++.h>
#define int long long
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
const int N = 4000;
struct node{
    int x, y;
}a[N], b[N];
bool cmp(node a, node b){
    if(a.x!=b.x) return a.x < b.x;
    return a.y < b.y;
}
int n, m, k, T;
char s[N][N];
signed main(){
    ios;
    cin >> n >> m >> k >> T;
    for(int i=1;i<=k;i++){
        cin >> a[i].x >> a[i].y;
    }
    for(int i=1;i<=k;i++) cin >> (s[i] + 1);
    for(int j=0;j<=T;j++){
        int num = 0;
        if(j==0){
        for(int i=1;i<=k;i++) b[i] = a[i];
             sort(b+1, b+k+1, cmp);
            for(int i=1;i<k;i++){
                if(b[i].x==b[i+1].x && b[i].y==b[i+1].y){
                    int cnt = 1, p = i+1;
                    while(b[p].x==b[i].x&&b[p].y==b[i].y&&p<=k){
                        p++, cnt++;
                    }
                    i = p - 1;
                    num += cnt*(cnt-1)/2;
                }
            }
        }else{
            for(int i=1;i<=k;i++){
                if(s[i][j]=='R') a[i].y++;
                else if(s[i][j]=='L') a[i].y--;
                else if(s[i][j]=='U') a[i].x--;
                else if(s[i][j]=='D') a[i].x++;
            }
            for(int i=1;i<=k;i++) b[i] = a[i];
            sort(b+1, b+k+1, cmp);
            for(int i=1;i<k;i++){
                if(b[i].x==b[i+1].x && b[i].y==b[i+1].y){
                    int cnt = 1, p = i+1;
                   
                    while(b[p].x==b[i].x&&b[p].y==b[i].y){
                        p++, cnt++;
                    }
                    num += cnt*(cnt-1)/2;
                    i = p - 1;
                }
            }
        }
        cout << num << '\n';
    }
    return 0;
}

K题:

思路:

我们是先用完全背包预处理了一定范围的结果, 这个范围不用太大, 当然我们预处理了1e5的数据, 但是实际只用到了前一百个数据, (事实上只用两三个数据应该就可以, 但是保险起见我们开大了一点)
以为要想硬币最少, 那么你用到的面值最大的硬币个数应该最多,用到最后剩余金额不足最大的面值, 这时再用小面值来凑, 要是小面值凑不出来, 就少用一个大面值硬币, 扩大一下范围再凑, 找到能凑出这个金额, 并且硬币最少的情况, 两人比较输出即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10, maxn = 1e6;
typedef long long ll;
ll f1[N], f2[N];
int a[] = {0, 2, 3, 17, 19};
int b[] = {0, 5, 7, 11, 13};
int main(){
    memset(f1, 0x3f, sizeof f1);
    memset(f2, 0x3f, sizeof f2);
    f1[0] = f2[0] = 0;
    for(int i=1;i<=4;i++)
        for(int j=a[i];j<=maxn;j++)
            f1[j] = min(f1[j], f1[j-a[i]]+1);
    for(int i=1;i<=4;i++)
        for(int j=b[i];j<=maxn;j++)
            f2[j] = min(f2[j], f2[j-b[i]]+1);
    int q; 
    scanf("%d", &q);
    while(q--){
        ll x;
        scanf("%lld",&x);
        ll tmp1 = x/19;
        ll md1 = x%19;
        ll tmp2 = x/13;
        ll md2 = x%13;
        ll mi1 = 0x3f3f3f3f;
        ll mi2 = 0x3f3f3f3f;
        for (int i = 0; i <= min(100ll, tmp1); ++i) {
            mi1 = min(f1[(i*19+md1)]+tmp1-i, mi1);
        }
        for (int i = 0; i <= min(100ll, tmp2); ++i) {
            mi2 = min(f2[(i*13+md2)]+tmp2-i, mi2);
        }
        if(mi1==0x3f3f3f3f&&mi2==0x3f3f3f3f) cout << -1 << '\n';
        else if(mi1==mi2) cout << "both" << '\n';
        else{
            if(mi1 < mi2) cout << "A" << '\n';
            else cout << "B" << '\n';
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值