2023年 8月18日普及组南外集训题解

A 陷阱

我们可以从 \(l\) 枚举到 \(d\),再计算是否满足要求,满足要求加入到数组中,输出第一个和最后一个

#include <iostream>
using namespace std;
const int N = 1e5 + 5;
int k;
int nums[N];
int main() {
    int l, d, x;
    cin >> l >> d >> x;
    for (int i = l; i <= d; i++) {
        int num = i;
        int sum = 0;
        while (num > 0) {
            sum += num % 10;
            num /= 10;
        }
        if (sum == x)
            nums[k++] = i;
    }
    cout << nums[0] << endl;
    cout << nums[k - 1];
    return 0;
}

B 翻转元素

我们可以使用动态规划来解决这个问题,\(f[i]\) 表示前 \(i\) 个数能达到的最大值,那么我们再加一维表示是否翻转
\(f[i][0]\) 表示没有翻转,如果前面一个数没有翻转,直接加 \(x\),如果翻转了加 \(-x\)
\(f[i][1]\) 表示翻转,如果前面一个数没有翻转,那么自己翻自己加 \(-x\),否则加 \(x\)
初始化:\(f[i][0]=x\) 没有翻转 \(f[i][1]=-x\) 翻转了
由于最少两个数翻转,那么最后的答案只能是不翻转的 \(f[n][0]\)

#include <iostream>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int f[N][2];
signed main() {
    int n;
    cin >> n;
    int x;
    cin >> x;
    f[1][0] = x;
    f[1][1] = -x;
    for (int i = 2; i <= n; i++) {
        cin >> x;
        f[i][0] = max(f[i - 1][0] + x, f[i - 1][1] - x);
        f[i][1] = max(f[i - 1][0] - x, f[i - 1][1] + x);
    }
    cout << f[n][0];
    return 0;
}

C 移动棋子

我们可以发现,想让移动的步数最小,就要让空隙最小。我们干脆把所有空隙大的放一个棋子不去移动,然后只用一个棋子移动。
为了避免负数这种麻烦的情况,我们直接对原数组排序,然后用一个数组计算所有的空隙,从大到小排序,然后选出 \(n-1\) 个空隙最大的摆上棋子,其余的加起来就行了

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
int x[N], nums[N];
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) cin >> x[i];
    sort(x + 1, x + m + 1);
    int res = 0;
    for (int i = 1; i < m; i++) {
        res += x[i + 1] - x[i];
        nums[i] = x[i + 1] - x[i];
    }
    sort(nums + 1, nums + m, greater<int>());
    for (int i = 1; i < n; i++) res -= nums[i];
    printf("%d", res);
    return 0;
}

D 插队

回忆一下,我们能单调栈解决的问题是找前面第一个比当前大的元素
而实际上我们也可以把它推广到这一题
考虑每一个被作为“前面第一个比当前大”的点,以它为最大值的点肯定是一个单调递增的序列
如以1为“前面第一个比当前大”的有2,3,4,构成一个单调的序列,而5自己也是一个递增序列
那么我们将2,3,4”挂“到节点1上,那么比2小的数肯定会比3小——我们可以再维护一个单调栈,维护当前”前面第一个比当前大“前面的元素,那么我们每次从中取最后一个较大的就是答案

#include <iostream>
#include <vector>
using namespace std;
const int N = 1000005;
int stk[N], res[N], h[N];
int tt;
vector<int> vec[N];
int main() {
    int n;
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &h[i]);
        while (tt && h[i] > h[stk[tt]]) tt--;
        vec[stk[tt]].push_back(i);
        stk[++tt] = i;
    }
    tt = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < vec[i].size(); j++) {
            int k = vec[i][j];
            while (tt && h[k] > h[stk[tt]]) tt--;
            res[k] = stk[tt];
        }
        stk[++tt] = i;
    }
    for (int i = 1; i <= n; i++) {
        int ans = res[i] + 1;
        printf("%lld\n", ans);
    }
    return 0;
}

E 小熊

我们可以使用宽搜来拓展出每个点位蜜蜂要多长时间到,然后再二分猜小熊可以停留在初始位置吃蜂蜜的最长时间
然后再使用一次宽搜进行检测是否可行
记得要初始化!!!

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 805;
char mp[N][N];  //地图
int lim[N][N];  //每个位置蜜蜂到的时间
bool st[N][N];  //是否走过
int sx, sy;     //起点位置
int n, s;
struct node {
    int x, y, u;
};
queue<node> q;
int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, 1, 0, -1 };
//预处理出每个点位蜜蜂要多久到达
void bfs() {
    while (!q.empty()) {
        node t = q.front();
        q.pop();
        lim[t.x][t.y] = t.u;  //将步数加进去
        for (int i = 0; i < 4; i++) {
            int x = t.x + dx[i], y = t.y + dy[i];
            // 1.是否走过 2.是否出界 3.是否是森林或小熊的老家
            if (st[x][y] || x < 1 || x > n || y < 1 || y > n || mp[x][y] == 'T' || mp[x][y] == 'D')
                continue;
            st[x][y] = true;  //打上标记
            q.push({ x, y, t.u + 1 });
        }
    }
}
bool check(int minute) {
    //上来就被抓住了
    if (minute >= lim[sx][sy])
        return false;
    q = queue<node>();  //清空队列再次使用
    q.push({ sx, sy, minute * s });
    memset(st, false, sizeof st);  //每次拓展需要初始化
    st[sx][sy] = true;
    while (!q.empty()) {
        node t = q.front();
        q.pop();
        //到家了
        if (mp[t.x][t.y] == 'D')
            return true;
        for (int i = 0; i < 4; i++) {
            int x = t.x + dx[i], y = t.y + dy[i];
            // 1.是否走过 2.是否出界 3.是否是草地 4.时间是否够(不会被抓住)5.是否走过
            if (x < 1 || x > n || y < 1 || y > n || (t.u + 1) / s >= lim[x][y] || mp[x][y] == 'T' || st[x][y])
                continue;
            st[x][y] = true;
            q.push({ x, y, t.u + 1 });
        }
    }
    return false;
}
int main() {
    memset(lim, 0x3f, sizeof lim);  //有些地方扩展不到
    scanf("%d%d", &n, &s);
    for (int i = 1; i <= n; i++) {
        scanf("%s", mp[i] + 1);
        for (int j = 1; j <= n; j++) {
            if (mp[i][j] == 'H')
                q.push({ i, j, 0 }), st[i][j] = true;  //记录蜂巢的位置
            if (mp[i][j] == 'M')
                sx = i, sy = j;  //记录起点
        }
    }
    bfs();
    int l = 0, r = n * n;  //二分猜时间
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (check(mid))
            l = mid;
        else
            r = mid - 1;
    }
    if (check(l))
        printf("%d", l);  //是否无解
    else
        printf("-1");
    return 0;
}
posted @ 2023-08-18 22:01  typerxiaozhu  阅读(35)  评论(0编辑  收藏  举报