USACO13NOV银组&金组(20230706模拟赛)题解

T1 圆形谷仓

洛谷题目链接

注意到题目中没有给出无解情况的输出,所以可知 Σ x ∗ y ≤ n \Sigma_{x*y} \leq n Σxyn

于是输入时就可以直接暴力求出每个房间被喜欢的奶牛头数,然后再暴力推两遍房间,因为是环形。

最后暴力找出最小的空余房间号,时间复杂度 O ( n ) O(n) O(n)

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN = 3000005;
const LL MAXK = 10005;
LL n, k;
LL r[MAXN];
int main() {
    scanf("%lld%lld", &n, &k);
    for (LL i = 1; i <= k; i++) {
        LL x, y, a, b;
        scanf("%lld%lld%lld%lld", &x, &y, &a, &b);
        for (LL j = 1; j <= y; j++) {
            LL num = (a * j % n + b % n) % n;
            r[num] += x;
        }
    }
    for (LL i = 0; i < n; i++) {
        if (r[i] != 0) {
            r[i + 1] += (r[i] - 1);
            r[i] = 1;
        }
    }
    if (r[n] != 0) {
        r[0] += r[n];
        for (LL i = 0; i < n; i++) {
            if (r[i] != 0) {
                r[i + 1] += (r[i] - 1);
                r[i] = 1;
            }
        }
    }
    for (LL i = 0; i < n; i++) {
        if (r[i] == 0) {
            printf("%lld", i);
            return 0;
        }
    }
}

T2 拥挤的奶牛

洛谷题目链接

依据题意,只需要维护每头奶牛左右两边 d d d距离以内的最大值即可。

每头奶牛好办,直接枚举就好了。左右两边也好办,优先队列维护,每次更新一下堆顶,只要保证堆顶元素在 d d d距离以内就行,队列里其他的元素不影响。

枚举是 O ( n ) O(n) O(n),由于每个元素进队出队各一次,所以也是 O ( n ) O(n) O(n),再加上优先队列的 O ( log ⁡ n ) O(\log n) O(logn),总时间复杂度大概是 O ( n log ⁡ n ) O(n \log n) O(nlogn),可以通过本题。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN = 50005;
LL n, d;
struct node {
    LL x, h;
    bool operator<(const node &t) const { return x < t.x; }
} a[MAXN];
priority_queue<pair<LL, LL> > q;
LL cnt[MAXN];
int main() {
    // freopen("B.in","r",stdin);
    // freopen("B.out","w",stdout);
    scanf("%lld%lld", &n, &d);
    for (LL i = 1; i <= n; i++) {
        scanf("%lld%lld", &a[i].x, &a[i].h);
    }
    sort(a + 1, a + n + 1);
    // for(LL i=1;i<=n;i++){
    //     cout<<a[i].x<<" "<<a[i].h<<endl;
    // }
    q.push({ a[1].h, a[1].x });
    for (LL i = 2; i <= n; i++) {
        while (!q.empty()) {
            LL t1 = q.top().first;
            LL t2 = q.top().second;
            q.pop();
            if (t2 + d >= a[i].x) {
                q.push({ t1, t2 });
                break;
            }
        }
        if (!q.empty()) {
            LL temp = q.top().first;
            if (temp >= 2 * a[i].h) {
                cnt[i]++;
            }
        }
        q.push({ a[i].h, a[i].x });
    }
    while (!q.empty()) {
        q.pop();
    }
    q.push({ a[n].h, a[n].x });
    for (LL i = n - 1; i >= 1; i--) {
        while (!q.empty()) {
            LL t1 = q.top().first;
            LL t2 = q.top().second;
            q.pop();
            if (t2 - d <= a[i].x) {
                q.push({ t1, t2 });
                break;
            }
        }
        if (!q.empty()) {
            LL temp = q.top().first;
            if (temp >= 2 * a[i].h) {
                cnt[i]++;
            }
        }
        q.push({ a[i].h, a[i].x });
    }
    LL ans = 0;
    for (LL i = 1; i <= n; i++) {
        if (cnt[i] == 2) {
            ans++;
        }
        // cout<<cnt[i]<<" ";
    }
    // cout<<endl;
    printf("%lld", ans);
}

T3 弹簧踩高跷

洛谷题目链接

首先想到的肯定是 O ( n 3 ) O(n^3) O(n3) D P DP DP,设 f [ i ] [ j ] f[i][j] f[i][j]表示当前从 i i i跳到 j j j以后的最大收益,转移方程也很好想。

但是 n ≤ 1000 n \leq 1000 n1000,无法通过,于是加上一点小优化,先修改一下循环范围,让循环次数变少(下面代码中最大循环循环次数为 166167000 166167000 166167000),然后再修改一下循环,让代码访问数组时尽量一行一行地访问,这样可以加快读取速度。于是就在速度极快的 O J OJ OJ上通过了。

然后就有了下面的赛时代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN = 1005;
struct node {
    LL x, p;
    bool operator<(const node &t) const { return x < t.x; }
} a[MAXN];
LL f[MAXN][MAXN];
LL n;
int main() {
    scanf("%lld", &n);
    LL ans = 0;
    for (LL i = 1; i <= n; i++) {
        scanf("%lld%lld", &a[i].x, &a[i].p);
        ans = max(ans, a[i].p);
    }
    sort(a + 1, a + n + 1);
    for (LL i = 1; i <= n; i++) {
        for (LL j = i + 1; j <= n; j++) {
            f[i][j] = f[j][i] = a[i].p + a[j].p;
        }
    }
    for (LL i = 1; i <= n; i++) {
        for (LL j = i + 1; j <= n; j++) {
            for (LL k = n; k >= j + 1; k--) {
                LL d1 = a[k].x - a[j].x;
                LL d2 = a[j].x - a[i].x;
                if (d1 >= d2) {
                    f[j][k] = max(f[i][j] + a[k].p, f[j][k]);
                } else {
                    break;
                }
            }
        }
    }
    for (LL i = n; i >= 1; i--) {
        for (LL j = i - 1; j >= 1; j--) {
            for (LL k = 1; k < j; k++) {
                LL d1 = a[i].x - a[j].x;
                LL d2 = a[j].x - a[k].x;
                if (d2 >= d1) {
                    f[j][k] = max(f[i][j] + a[k].p, f[j][k]);
                } else {
                    break;
                }
            }
        }
    }
    for (LL i = 1; i <= n; i++) {
        for (LL j = 1; j <= n; j++) {
            ans = max(ans, f[i][j]);
        }
    }
    printf("%lld", ans);
}

但是,这是一道2013年的题目,所以正解的时间复杂度应为 O ( n 2 ) O(n^2) O(n2),我们不能止步于此。

重新定义一下,设 f [ i ] [ j ] f[i][j] f[i][j]表示到达第 i i i个点,并且是从第 j j j个点转移过来的最大得分。

于是 f [ i ] [ j ] = max ⁡ ( f [ j ] [ k ] + a [ i ] . p ) f[i][j]=\max(f[j][k]+a[i].p) f[i][j]=max(f[j][k]+a[i].p)

显然 f [ i ] [ j ] = max ⁡ ( f [ j ] [ k ] ) + a [ i ] . p f[i][j]=\max(f[j][k])+a[i].p f[i][j]=max(f[j][k])+a[i].p

又因为 f [ i − 1 ] [ j ] = max ⁡ ( f [ j ] [ k ] ) + a [ i − 1 ] . p f[i-1][j]=\max(f[j][k])+a[i-1].p f[i1][j]=max(f[j][k])+a[i1].p

和原来式子非常像,所以可以 f [ i ] [ j ] = f [ i − 1 ] [ j ] − a [ i − 1 ] . p + a [ i ] . p f[i][j]=f[i-1][j]-a[i-1].p+a[i].p f[i][j]=f[i1][j]a[i1].p+a[i].p,但是这个方程的定义范围是 a [ j ] . x − a [ k ] . x ≤ a [ i − 1 ] . x − a [ j ] . x a[j].x-a[k].x \leq a[i-1].x-a[j].x a[j].xa[k].xa[i1].xa[j].x,又因为 a [ i − 1 ] . x < a [ i ] . x a[i-1].x < a[i].x a[i1].x<a[i].x,满足条件的 k k k会变多,所以我们需要先将拓展到的 k k k取一个最大值再转移。

正反各做一遍 D P DP DP就行。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MAXN = 1005;
struct node {
    LL x, p;
    bool operator<(const node &t) const { return x < t.x; }
} a[MAXN];
LL f[MAXN][MAXN];
LL n;
int main() {
    scanf("%lld", &n);
    LL ans = 0;
    for (LL i = 1; i <= n; i++) {
        scanf("%lld%lld", &a[i].x, &a[i].p);
        ans = max(ans, a[i].p);
    }
    sort(a + 1, a + n + 1);
    for (LL i = 1; i <= n; i++) {
        for (LL j = 1; j <= n; j++) {
            if (i != j)
                f[i][j] = a[i].p + a[j].p;
            else
                f[i][j] = a[i].p;
        }
    }
    for (LL j = 1; j <= n; j++) {
        for (LL i = j + 1, xx = j + 1; i <= n; i++) {
            f[i][j] = f[i - 1][j] - a[i - 1].p;
            while (xx > 1 && a[j].x - a[xx - 1].x <= a[i].x - a[j].x) {
                f[i][j] = max(f[i][j], f[j][--xx]);
            }
            f[i][j] += a[i].p;
            ans = max(ans, f[i][j]);
        }
    }
    for (LL j = n; j >= 1; j--) {
        for (LL i = j - 1, xx = j - 1; i >= 1; i--) {
            f[i][j] = f[i + 1][j] - a[i + 1].p;
            while (xx < n && a[xx + 1].x - a[j].x <= a[j].x - a[i].x) {
                f[i][j] = max(f[i][j], f[j][++xx]);
            }
            f[i][j] += a[i].p;
            ans = max(ans, f[i][j]);
        }
    }
    printf("%lld", ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值